Spring boot 2.0 整合 Security JWT 前后分離 認(rèn)證授權(quán)

Spring Security 簡(jiǎn)介

Spring Security 主要作用是認(rèn)證與授權(quán)
Spring Security 和 jwt 相關(guān)的介紹自行百度吧

下面直接上代碼,注意看注釋 有相關(guān)的代碼作用解釋赛糟,如果有錯(cuò)誤的地方,請(qǐng)指出 一起學(xué)習(xí) 謝謝客给!

認(rèn)證大概流程

認(rèn)證流程.jpg

結(jié)構(gòu)目錄

image.png

pom.xml

<properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <security-jwt.version>1.0.9.RELEASE</security-jwt.version>
        <jjwt.version>0.9.0</jjwt.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-oauth2</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-jwt</artifactId>
            <version>${security-jwt.version}</version>
        </dependency>

        <dependency>
            <groupId>io.jsonwebtoken</groupId>
            <artifactId>jjwt</artifactId>
            <version>${jjwt.version}</version>
        </dependency>
   
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>

application.yml

server:
  port: 18081

spring:
  application:
    name: oauth-server   # 應(yīng)用名稱

  jpa:
      open-in-view: true
      database: POSTGRESQL
      show-sql: true
      hibernate:
        ddl-auto: update
        dialect: org.hibernate.dialect.PostgreSQLDialect
      properties:
        hibernate:
          temp:
            use_jdbc_metadata_defaults: false

  # 數(shù)據(jù)源 配置
  datasource:
      platform: postgres
      url: jdbc:postgresql://127.0.0.1:5432/cloud_oauth2?useUnicode=true&characterEncoding=utf-8
      username: postgres
      password: postgres123
      driver-class-name: org.postgresql.Driver

  # redis 配置
  redis:
    host: 127.0.0.1
    port: 6379
    password:
    database: 0
    jedis:
      pool:
        #最大連接數(shù)
        max-active: 8
        #最大空閑
        max-idle: 8
        #最大阻塞等待時(shí)間(負(fù)數(shù)表示沒(méi)限制)
        max-wait: -1ms
        #最小空閑
        min-idle: 0
    #連接超時(shí)時(shí)間
    timeout: 1000ms



# JWT 配置
jwt:
  # 存放Token的Header Key
  header: Authorization
  # 密匙key
  secret: mySecret
  # 過(guò)期時(shí)間   單位秒 7天后過(guò)期  604800
  expiration: 3600
  # 自定義token 前綴字符
  tokenHead: Bearer-
  # 超時(shí)時(shí)間   單位秒
  access_token: 3600
  # 刷新token時(shí)間   單位秒
  refresh_token: 3600
  route:
    authentication:
      path: login/entry
      refresh: oauth/refresh
      register: login/account


# 配置不需要認(rèn)證的接口
com:
  example:
    oauth:
      security:
        antMatchers:
          /auth/v1/api/login/**,
          /auth/v1/api/module/tree/**,
          /auth/v1/api/grid/**


# 日志
logging:
  level:
    org:
      springframework:
        security: DEBUG

必須的配置類

  • web安全配置類 WebSecurityConfig
  • 用戶身份權(quán)限認(rèn)證類 MyUserDetailService
  • 資源權(quán)限認(rèn)證器 MyAccessDecisionManager
  • 請(qǐng)求過(guò)濾類 MyFilterSecurityInterceptor
  • 加載資源與權(quán)限的關(guān)系 MyInvocationSecurityMetadataSourceService

可選的處理類

  • 權(quán)限不足處理類 MyAccessDeniedHandler
  • 異常處理類 MyAuthenticationException
  • 登錄成功后處理類 MyAuthenticationSuccessHandler
  • 登錄失敗后處理類 MyAuthenticationFailureHandler
  • 退出系統(tǒng)成功后處理類 MyLogoutSuccessHandler
  • 登錄驗(yàn)證(比如校驗(yàn)驗(yàn)證碼) MyUsernamePasswordAuthenticationFilter
  • 認(rèn)證失敗處理類 MyAuthenticationEntryPointHandler

整合jwt 需要的類

  • jwt 工具類 提供校驗(yàn)toeken 蓖宦、生成token、根據(jù)token獲取用戶等方法 JwtTokenUtil
  • 用戶信息 JWTUserDetails
  • JWTUserDetailsFactory
  • 對(duì)請(qǐng)求的token進(jìn)行校驗(yàn) JwtAuthenticationTokenFilter

MyAccessDecisionManager 資源權(quán)限認(rèn)證器 認(rèn)證用戶是否擁有所請(qǐng)求資源的權(quán)限

/***
 *
 * @FileName: MyAccessDecisionManager
 * @Company:
 * @author    
 * @Date      2018年05月11日
 * @version   1.0.0
 * @remark:   資源權(quán)限認(rèn)證器  證用戶是否擁有所請(qǐng)求資源的權(quán)限
 * @explain   接口AccessDecisionManager也是必須實(shí)現(xiàn)的诸老。 decide方法里面寫(xiě)的就是授權(quán)策略了界睁,需要什么策略觉增,可以自己寫(xiě)其中的策略邏輯
 *             認(rèn)證通過(guò)就返回,不通過(guò)拋異常就行了翻斟,spring security會(huì)自動(dòng)跳到權(quán)限不足處理類(WebSecurityConfig 類中 配置文件上配的)
 *
 *
 */
@Slf4j
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {

    /**
     *  授權(quán)策略
     *
     * decide()方法在url請(qǐng)求時(shí)才會(huì)調(diào)用逾礁,服務(wù)器啟動(dòng)時(shí)不會(huì)執(zhí)行這個(gè)方法
     *
     * @param configAttributes 裝載了請(qǐng)求的url允許的角色數(shù)組 。這里是從MyInvocationSecurityMetadataSource里的loadResourceDefine方法里的atts對(duì)象取出的角色數(shù)據(jù)賦予給了configAttributes對(duì)象
     * @param object url
     * @param authentication 裝載了從數(shù)據(jù)庫(kù)讀出來(lái)的權(quán)限(角色) 數(shù)據(jù)。這里是從MyUserDetailService里的loadUserByUsername方法里的grantedAuths對(duì)象的值傳過(guò)來(lái)給 authentication 對(duì)象,簡(jiǎn)單點(diǎn)就是從spring的全局緩存SecurityContextHolder中拿到的嘹履,里面是用戶的權(quán)限信息
     *
     * 注意: Authentication authentication 如果是前后端分離 則有跨域問(wèn)題腻扇,跨域情況下 authentication 無(wú)法獲取當(dāng)前登陸人的身份認(rèn)證(登陸成功后),我嘗試用token來(lái)效驗(yàn)權(quán)限
     *
     */
    @Override
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException, InsufficientAuthenticationException {
        // 無(wú)權(quán)限訪問(wèn)
        if(CollectionUtils.isEmpty(configAttributes)){
             log.info("無(wú)訪問(wèn)權(quán)限.");
            throw new AccessDeniedException("無(wú)訪問(wèn)權(quán)限.");
        }
        Iterator<ConfigAttribute> iterator = configAttributes.iterator();
        while (iterator.hasNext()){
            ConfigAttribute configAttribute = iterator.next();
            String needRole = configAttribute.getAttribute();
            for(GrantedAuthority grantedAuthority : authentication.getAuthorities()){
                //grantedAuthority 為用戶所被賦予的權(quán)限砾嫉。 needRole 為訪問(wèn)相應(yīng)的資源應(yīng)該具有的權(quán)限幼苛。
                //判斷兩個(gè)請(qǐng)求的url的權(quán)限和用戶具有的權(quán)限是否相同,如相同焕刮,允許訪問(wèn) 權(quán)限就是那些以ROLE_為前綴的角色
                if (needRole.trim().equals(grantedAuthority.getAuthority().trim())){
                    //匹配到對(duì)應(yīng)的角色舶沿,則允許通過(guò)
                    return;
                }
            }
        }
        //該url具有訪問(wèn)權(quán)限,但是當(dāng)前登錄用戶沒(méi)有匹配到URL對(duì)應(yīng)的權(quán)限配并,則拋出無(wú)權(quán)限錯(cuò)誤
        log.info("無(wú)訪問(wèn)權(quán)限.");
        throw  new AccessDeniedException("無(wú)訪問(wèn)權(quán)限.");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

MyInvocationSecurityMetadataSourceService 加載資源與權(quán)限的對(duì)應(yīng)關(guān)系

/***
 *
 * @FileName: MyInvocationSecurityMetadataSourceService
 * @Company:
 * @author    
 * @Date      2018年05月11日
 * @version   1.0.0
 * @remark:   加載資源與權(quán)限的對(duì)應(yīng)關(guān)系
 * @explain 實(shí)現(xiàn)FilterInvocationSecurityMetadataSource接口也是必須的括荡。 首先,這里從數(shù)據(jù)庫(kù)中獲取信息溉旋。 其中l(wèi)oadResourceDefine方法不是必須的畸冲,
 *           這個(gè)只是加載所有的資源與權(quán)限的對(duì)應(yīng)關(guān)系并緩存起來(lái),避免每次獲取權(quán)限都訪問(wèn)數(shù)據(jù)庫(kù)(提高性能)观腊,然后getAttributes根據(jù)參數(shù)(被攔截url)返回權(quán)限集合邑闲。
 *           這種緩存的實(shí)現(xiàn)其實(shí)有一個(gè)缺點(diǎn),因?yàn)閘oadResourceDefine方法是放在構(gòu)造器上調(diào)用的梧油,而這個(gè)類的實(shí)例化只在web服務(wù)器啟動(dòng)時(shí)調(diào)用一次苫耸,那就是說(shuō)loadResourceDefine方法只會(huì)調(diào)用一次,
 *           如果資源和權(quán)限的對(duì)應(yīng)關(guān)系在啟動(dòng)后發(fā)生了改變儡陨,那么緩存起來(lái)的權(quán)限數(shù)據(jù)就和實(shí)際授權(quán)數(shù)據(jù)不一致鲸阔,那就會(huì)授權(quán)錯(cuò)誤了。但如果資源和權(quán)限對(duì)應(yīng)關(guān)系是不會(huì)改變的迄委,這種方法性能會(huì)好很多。
 *           要想解決 權(quán)限數(shù)據(jù)的一致性 可以直接在getAttributes方法里面調(diào)用數(shù)據(jù)庫(kù)操作獲取權(quán)限數(shù)據(jù)类少,通過(guò)被攔截url獲取數(shù)據(jù)庫(kù)中的所有權(quán)限叙身,封裝成Collection<ConfigAttribute>返回就行了。(靈活硫狞、簡(jiǎn)單
 *
 *           器啟動(dòng)加載順序:1:調(diào)用loadResourceDefine()方法  2:調(diào)用supports()方法   3:調(diào)用getAllConfigAttributes()方法
 *
 *
 */
@Slf4j
@Component
public class MyInvocationSecurityMetadataSourceService implements FilterInvocationSecurityMetadataSource {
    //存放資源配置對(duì)象
    private static Map<String, Collection<ConfigAttribute>> resourceMap = null;
    @Autowired
    private ModuleService moduleService;
    @Autowired
    private RoleService roleService;
    @Autowired
    private UrlMatcher urlMatcher;

    /**
     * 參數(shù)是要訪問(wèn)的url信轿,返回這個(gè)url對(duì)于的所有權(quán)限(或角色)
     * 每次請(qǐng)求后臺(tái)就會(huì)調(diào)用 得到請(qǐng)求所擁有的權(quán)限
     * 這個(gè)方法在url請(qǐng)求時(shí)才會(huì)調(diào)用,服務(wù)器啟動(dòng)時(shí)不會(huì)執(zhí)行這個(gè)方法
     * getAttributes這個(gè)方法會(huì)根據(jù)你的請(qǐng)求路徑去獲取這個(gè)路徑應(yīng)該是有哪些權(quán)限才可以去訪問(wèn)残吩。
     *
     */
    @Override
    public Collection<ConfigAttribute> getAttributes(Object object) throws IllegalArgumentException {
       // if (resourceMap == null){  //取消這段代碼注釋 情況下 每次服務(wù)啟動(dòng)后請(qǐng)求后臺(tái)只有到數(shù)據(jù)庫(kù)中取一次權(quán)限   如果注釋掉這段代碼則每次請(qǐng)求都會(huì)到數(shù)據(jù)庫(kù)中取權(quán)限
            loadResourceDefine();  // 每次請(qǐng)求 都會(huì)去數(shù)據(jù)庫(kù)查詢權(quán)限  貌似很耗性能
       // }
        // object 是一個(gè)URL财忽,被用戶請(qǐng)求的url。
        String url = ((FilterInvocation) object).getRequestUrl();
        log.info("請(qǐng)求 url :" + url);
        int firstQuestionMarkIndex = url.indexOf("?");
        if (firstQuestionMarkIndex != -1) {
            url = url.substring(0, firstQuestionMarkIndex);
        }
        //循環(huán)已有的角色配置對(duì)象 進(jìn)行url匹配
        Iterator<String> ite = resourceMap.keySet().iterator();
        while (ite.hasNext()) {
            String resURL = ite.next().trim();
            if (urlMatcher.pathMatchesUrl(resURL, url)) {     // 路徑支持Ant風(fēng)格的通配符 /spitters/**
                return resourceMap.get(resURL);
            }
           /* if (url.equals(resURL)) {   // 路徑不支持Ant風(fēng)格的通配符
                //返回當(dāng)前 url  所需要的權(quán)限
                return resourceMap.get(resURL);
            }*/
        }
        return null ;
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
       //要返回true  不然要報(bào)異称辏   SecurityMetadataSource does not support secure object class: class
        return true;
    }


    /**
     * 初始化資源 ,提取系統(tǒng)中的所有權(quán)限即彪,加載所有url和權(quán)限(或角色)的對(duì)應(yīng)關(guān)系,  web容器啟動(dòng)就會(huì)執(zhí)行
     * 如果啟動(dòng)@PostConstruct 注解   則web容器啟動(dòng)就會(huì)執(zhí)行
     */
    //@PostConstruct
    public void loadResourceDefine() {
       // if (resourceMap == null) {
            //應(yīng)當(dāng)是資源為key活尊, 權(quán)限為value隶校。 資源通常為url漏益, 權(quán)限就是那些以ROLE_為前綴的角色。 一個(gè)資源可以由多個(gè)權(quán)限來(lái)訪問(wèn)深胳。
            resourceMap = new ConcurrentHashMap<>();
            //獲取所有分配的角色
            List<SysRole> roleList = this.roleService.findByRoleModule();
            //容器啟動(dòng)時(shí),獲取全部系統(tǒng)菜單資源信息
            List<SysModuleVO> moduleList = this.moduleService.findByRoleModule();
            if (!CollectionUtils.isEmpty(roleList)){
                for (SysRole role : roleList){
                    //授權(quán)標(biāo)識(shí)
                    String authorizedSigns = role.getAuthorizedSigns().trim();
                    ConfigAttribute configAttributes = new SecurityConfig(authorizedSigns);
                    for (SysModuleVO module : moduleList){
                        boolean flag = String.valueOf(role.getId()).equals(module.getAuthorizedSigns());
                        if(flag){
                            //請(qǐng)求url
                            String url =StringUtils.isNotBlank(module.getMenuUrl()) ? module.getMenuUrl().trim() : "";
                            // 判斷資源文件和權(quán)限的對(duì)應(yīng)關(guān)系绰疤,如果已經(jīng)存在相關(guān)的資源url,則要通過(guò)該url為key提取出權(quán)限集合舞终,將權(quán)限增加到權(quán)限集合中轻庆。
                            if (resourceMap.containsKey(url)) {
                                Collection<ConfigAttribute> value = resourceMap.get(url);
                                value.add(configAttributes);
                                resourceMap.put(url, value);
                            } else {
                                Collection<ConfigAttribute> atts = new ArrayList<ConfigAttribute>();
                                atts.add(configAttributes);
                                resourceMap.put(url, atts);
                            }
                        }
                    }
                }
            }

      //  }

    }

}

MyFilterSecurityInterceptor 請(qǐng)求過(guò)濾

/***
 *
 * @FileName: MyFilterSecurityInterceptor
 * @Company:
 * @author    
 * @Date      2018年05月11日
 * @version   1.0.0
 * @remark:   過(guò)濾用戶請(qǐng)求
 * @explain   繼承AbstractSecurityInterceptor、實(shí)現(xiàn)Filter是必須的
 *             首先敛劝,登陸后余爆,每次訪問(wèn)資源都會(huì)被這個(gè)攔截器攔截,會(huì)執(zhí)行doFilter這個(gè)方法攘蔽,這個(gè)方法調(diào)用了invoke方法龙屉,其中fi斷點(diǎn)顯示是一個(gè)url
 *             最重要的是beforeInvocation這個(gè)方法,它首先會(huì)調(diào)用MyInvocationSecurityMetadataSource類的getAttributes方法獲取被攔截url所需的權(quán)限
 *             在調(diào)用MyAccessDecisionManager類decide方法判斷用戶是否具有權(quán)限,執(zhí)行完后就會(huì)執(zhí)行下一個(gè)攔截器
 *
 *
 */
@Component
public class MyFilterSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {

    }

    /**
     * 登錄后 每次請(qǐng)求都會(huì)調(diào)用這個(gè)攔截器進(jìn)行請(qǐng)求過(guò)濾
     * @param servletRequest
     * @param servletResponse
     * @param filterChain
     * @throws IOException
     * @throws ServletException
     */
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(fi);
    }

    @Override
    public void destroy() {

    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

   /* @Override
    public void setAccessDecisionManager(MyAccessDecisionManager accessDecisionManager) {
        super.setAccessDecisionManager(this.accessDecisionManager);
    }*/
    @Autowired
    public void setAccessDecisionManager(MyAccessDecisionManager myAccessDecisionManager) {
        super.setAccessDecisionManager(myAccessDecisionManager);
    }


    /**
     * 攔截請(qǐng)求處理
     * @param fi
     * @throws IOException
     * @throws ServletException
     */
    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //fi里面有一個(gè)被攔截的url
        //里面調(diào)用MyInvocationSecurityMetadataSource的getAttributes(Object object)這個(gè)方法獲取fi對(duì)應(yīng)的所有權(quán)限
        //再調(diào)用MyAccessDecisionManager的decide方法來(lái)校驗(yàn)用戶的權(quán)限是否足夠
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //執(zhí)行下一個(gè)攔截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }
}

MyUserDetailService 用戶登錄身份認(rèn)證


/***
 *
 * @FileName: MyUserDetailService
 * @Company:
 * @author    
 * @Date      2018年05月11日
 * @version   1.0.0
 * @remark:   配置用戶權(quán)限認(rèn)證
 * @explain   當(dāng)用戶登錄時(shí)會(huì)進(jìn)入此類的loadUserByUsername方法對(duì)用戶進(jìn)行驗(yàn)證满俗,驗(yàn)證成功后會(huì)被保存在當(dāng)前回話的principal對(duì)象中
 *             系統(tǒng)獲取當(dāng)前登錄對(duì)象信息方法 WebUserDetails webUserDetails = (WebUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 *
 *              異常信息:
 *              UsernameNotFoundException     用戶找不到
 *              BadCredentialsException       壞的憑據(jù)
 *              AccountExpiredException       賬戶過(guò)期
 *              LockedException               賬戶鎖定
 *              DisabledException             賬戶不可用
 *              CredentialsExpiredException   證書(shū)過(guò)期
 *
 *
 */
@Slf4j
@Service("myUserDetailService")
public class MyUserDetailService implements UserDetailsService {

    @Autowired
    private UserRoleService userRoleService;

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        log.info("登錄用戶:" + username);
        //用戶用戶信息和用戶角色
        UserRoleVO userRole = this.userRoleService.findUserAndRole(username);
        if(userRole.getUserId() == null){
            //后臺(tái)拋出的異常是:org.springframework.security.authentication.BadCredentialsException: Bad credentials  壞的憑證 如果要拋出UsernameNotFoundException 用戶找不到異常則需要自定義重新它的異常
            log.info("登錄用戶:" + username + " 不存在.");
            throw new UsernameNotFoundException("登錄用戶:" + username + " 不存在");
        }

        //獲取用戶信息
        UserInfoVO userInfo = userRole.getUserInfo();
        //獲取用戶擁有的角色
        List<RoleVO> roleList = userRole.getRoles();
        Set<GrantedAuthority> grantedAuths = new HashSet<GrantedAuthority>();
        if(roleList.size() > 0){
            roleList.stream().forEach(role ->{
                grantedAuths.add(new SimpleGrantedAuthority(role.getAuthorizedSigns()));
            });
        }
        User userDetail = new User(userInfo.getUserAccount(),userInfo.getUserPwd(),
                grantedAuths);

       //不使用jwt 代碼
       //return userDetail;


        //使用JWT 代碼
        UserDetail user = DozerBeanMapperUtil.copyProperties(userInfo,UserDetail.class);
        user.setUserId(userInfo.getId());
        return JWTUserDetailsFactory.create(userDetail,user);
    }

}

WebSecurityConfig web 安全配置

/***
 *
 * @FileName: WebSecurityConfig
 * @Company:
 * @author    ljy
 * @Date      2018年05月11日
 * @version   1.0.0
 * @remark:   web 安全性配置
 * @explain   當(dāng)用戶登錄時(shí)會(huì)進(jìn)入此類的loadUserByUsername方法對(duì)用戶進(jìn)行驗(yàn)證转捕,驗(yàn)證成功后會(huì)被保存在當(dāng)前回話的principal對(duì)象中
 *             系統(tǒng)獲取當(dāng)前登錄對(duì)象信息方法 WebUserDetails webUserDetails = (WebUserDetails)SecurityContextHolder.getContext().getAuthentication().getPrincipal();
 *
 */

@Configuration
@EnableWebSecurity  //啟動(dòng)web安全性
@EnableGlobalMethodSecurity(prePostEnabled = true)  //開(kāi)啟方法級(jí)的權(quán)限注解  性設(shè)置后控制器層的方法前的@PreAuthorize("hasRole('admin')") 注解才能起效
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private MyUserDetailService myUserDetailService;
    @Autowired
    private MyFilterSecurityInterceptor myFilterSecurityInterceptor;

    @Value("${jwt.route.authentication.path}")
    private String authenticationPath;

    // 不需要認(rèn)證的接口
    @Value("${com.example.oauth.security.antMatchers}")
    private String antMatchers;

    /**
     * 置user-detail服務(wù)
     *
     * 方法描述
     * accountExpired(boolean)                定義賬號(hào)是否已經(jīng)過(guò)期
     * accountLocked(boolean)                 定義賬號(hào)是否已經(jīng)鎖定
     * and()                                  用來(lái)連接配置
     * authorities(GrantedAuthority...)       授予某個(gè)用戶一項(xiàng)或多項(xiàng)權(quán)限
     * authorities(List)                      授予某個(gè)用戶一項(xiàng)或多項(xiàng)權(quán)限
     * authorities(String...)                 授予某個(gè)用戶一項(xiàng)或多項(xiàng)權(quán)限
     * disabled(boolean)                      定義賬號(hào)是否已被禁用
     * withUser(String)                       定義用戶的用戶名
     * password(String)                       定義用戶的密碼
     * roles(String...)                       授予某個(gè)用戶一項(xiàng)或多項(xiàng)角色
     *
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        //super.configure(auth);
        // 配置指定用戶權(quán)限信息  通常生產(chǎn)環(huán)境都是從數(shù)據(jù)庫(kù)中讀取用戶權(quán)限信息而不是在這里配置
        //auth.inMemoryAuthentication().withUser("username1").password("123456").roles("USER").and().withUser("username2").password("123456").roles("USER","AMDIN");

        // ****************   基于數(shù)據(jù)庫(kù)中的用戶權(quán)限信息 進(jìn)行認(rèn)證
        //指定密碼加密所使用的加密器為 bCryptPasswordEncoder()
        //需要將密碼加密后寫(xiě)入數(shù)據(jù)庫(kù)
        // myUserDetailService 類中獲取了用戶的用戶名、密碼以及是否啟用的信息唆垃,查詢用戶所授予的權(quán)限五芝,用來(lái)進(jìn)行鑒權(quán),查詢用戶作為群組成員所授予的權(quán)限
        auth.userDetailsService(myUserDetailService).passwordEncoder(bCryptPasswordEncoder());
        //不刪除憑據(jù)辕万,以便記住用戶
        auth.eraseCredentials(false);

    }

    /**
     * 配置Spring Security的Filter鏈
     * @param web
     * @throws Exception
     */
    @Override
    public void configure(WebSecurity web) throws Exception {
        //解決靜態(tài)資源被攔截的問(wèn)題
        web.ignoring().antMatchers("/favicon.ico");
        web.ignoring().antMatchers("/error");
        super.configure(web);
    }

    /**
     *  解決 無(wú)法直接注入 AuthenticationManager
     * @return
     * @throws Exception
     */
    @Bean(name = BeanIds.AUTHENTICATION_MANAGER)
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }

    /**
     * 配置如何通過(guò)攔截器保護(hù)請(qǐng)求
     * 指定哪些請(qǐng)求需要認(rèn)證枢步,哪些請(qǐng)求不需要認(rèn)證,以及所需要的權(quán)限
     * 通過(guò)調(diào)用authorizeRequests()和anyRequest().authenticated()就會(huì)要求所有進(jìn)入應(yīng)用的HTTP請(qǐng)求都要進(jìn)行認(rèn)證
     *
     * 方法描述
     * anonymous()                                        允許匿名用戶訪問(wèn)
     * authenticated()                                    允許經(jīng)過(guò)認(rèn)證的用戶訪問(wèn)
     * denyAll()                                          無(wú)條件拒絕所有訪問(wèn)
     * fullyAuthenticated()                如果用戶是完整的話(不是通過(guò)Remember-me功能認(rèn)證的)渐尿,就允許訪問(wèn)
     * hasAnyAuthority(String...)                 如果用戶具備給定權(quán)限中的某一個(gè)的話醉途,就允許訪問(wèn)
     * hasAnyRole(String...)                    如果用戶具備給定角色中的某一個(gè)的話,就允許訪問(wèn)
     * hasAuthority(String)                     如果用戶具備給定權(quán)限的話砖茸,就允許訪問(wèn)
     * hasIpAddress(String)                    如果請(qǐng)求來(lái)自給定IP地址的話隘擎,就允許訪問(wèn)
     * hasRole(String)                        如果用戶具備給定角色的話,就允許訪問(wèn)
     * not()                               對(duì)其他訪問(wèn)方法的結(jié)果求反
     * permitAll()                           無(wú)條件允許訪問(wèn)
     * rememberMe()                          如果用戶是通過(guò)Remember-me功能認(rèn)證的凉夯,就允許訪問(wèn)
     *
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        System.out.println("不需要認(rèn)證的url:"+antMatchers);
        //super.configure(http);
        //關(guān)閉csrf驗(yàn)證
        http.csrf().disable()
                // 基于token货葬,所以不需要session  如果基于session 則表使用這段代碼
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                //對(duì)請(qǐng)求進(jìn)行認(rèn)證  url認(rèn)證配置順序?yàn)椋?.先配置放行不需要認(rèn)證的 permitAll() 2.然后配置 需要特定權(quán)限的 hasRole() 3.最后配置 anyRequest().authenticated()
                .authorizeRequests()
                // 所有 /oauth/v1/api/login/ 請(qǐng)求的都放行 不做認(rèn)證即不需要登錄即可訪問(wèn)
                .antMatchers(antMatchers.split(",")).permitAll()
                //.antMatchers("/auth/v1/api/login/**","/auth/v1/api/module/tree/**","/auth/v1/api/grid/**").permitAll()
                // 對(duì)于獲取token的rest api要允許匿名訪問(wèn)
                .antMatchers("oauth/**").permitAll()
                // 其他請(qǐng)求都需要進(jìn)行認(rèn)證,認(rèn)證通過(guò)夠才能訪問(wèn)   待考證:如果使用重定向 httpServletRequest.getRequestDispatcher(url).forward(httpServletRequest,httpServletResponse); 重定向跳轉(zhuǎn)的url不會(huì)被攔截(即在這里配置了重定向的url需要特定權(quán)限認(rèn)證不起效),但是如果在Controller 方法上配置了方法級(jí)的權(quán)限則會(huì)進(jìn)行攔截
                .anyRequest().authenticated()
                .and().exceptionHandling()
                // 認(rèn)證配置當(dāng)用戶請(qǐng)求了一個(gè)受保護(hù)的資源劲够,但是用戶沒(méi)有通過(guò)登錄認(rèn)證震桶,則拋出登錄認(rèn)證異常,MyAuthenticationEntryPointHandler類中commence()就會(huì)調(diào)用
                .authenticationEntryPoint(myAuthenticationEntryPoint())
                //用戶已經(jīng)通過(guò)了登錄認(rèn)證征绎,在訪問(wèn)一個(gè)受保護(hù)的資源蹲姐,但是權(quán)限不夠,則拋出授權(quán)異常,MyAccessDeniedHandler類中handle()就會(huì)調(diào)用
                .accessDeniedHandler(myAccessDeniedHandler())
                .and()
                //
                .formLogin()
                // 登錄url
                .loginProcessingUrl("/auth/v1/api/login/entry")  // 此登錄url 和Controller 無(wú)關(guān)系
               // .loginProcessingUrl("/auth/v1/api/login/enter")  //使用自己定義的Controller 中的方法 登錄會(huì)進(jìn)入Controller 中的方法
                // username參數(shù)名稱 后臺(tái)接收前端的參數(shù)名
                .usernameParameter("userAccount")
                //登錄密碼參數(shù)名稱 后臺(tái)接收前端的參數(shù)名
                .passwordParameter("userPwd")
                //登錄成功跳轉(zhuǎn)路徑
                .successForwardUrl("/")
                //登錄失敗跳轉(zhuǎn)路徑
                .failureUrl("/")
                //登錄頁(yè)面路徑
                .loginPage("/")
                .permitAll()
                //登錄成功后 MyAuthenticationSuccessHandler類中onAuthenticationSuccess()被調(diào)用
                .successHandler(myAuthenticationSuccessHandler())
                //登錄失敗后 MyAuthenticationFailureHandler 類中onAuthenticationFailure()被調(diào)用
                .failureHandler(myAuthenticationFailureHandler())
                .and()
                .logout()
                //退出系統(tǒng)url
                .logoutUrl("/auth/v1/api/login/logout")
                //退出系統(tǒng)后的url跳轉(zhuǎn)
                .logoutSuccessUrl("/")
                //退出系統(tǒng)后的 業(yè)務(wù)處理
                .logoutSuccessHandler(myLogoutSuccessHandler())
                .permitAll()
                .invalidateHttpSession(true)
                .and()
                //登錄后記住用戶淤堵,下次自動(dòng)登錄,數(shù)據(jù)庫(kù)中必須存在名為persistent_logins的表
                // 勾選Remember me登錄會(huì)在PERSISTENT_LOGINS表中寝衫,生成一條記錄
                .rememberMe()
                //cookie的有效期(秒為單位
                .tokenValiditySeconds(3600);
        // 加入自定義UsernamePasswordAuthenticationFilter替代原有Filter
        http.addFilterAt(myUsernamePasswordAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
        //在 beforeFilter 之前添加 自定義 filter
        http.addFilterBefore(myFilterSecurityInterceptor, FilterSecurityInterceptor.class);
        // 添加JWT filter 驗(yàn)證其他請(qǐng)求的Token是否合法
        http.addFilterBefore(authenticationTokenFilterBean(), FilterSecurityInterceptor.class);
        // 禁用緩存
        http.headers().cacheControl();


    }


    /**
     * 密碼加密方式
     * @return
     */
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder() {
        return new BCryptPasswordEncoder();
    }


    /**
     * 注冊(cè)  登錄認(rèn)證 bean
     * @return
     */
    @Bean
    public AuthenticationEntryPoint myAuthenticationEntryPoint(){

        //return new MyAuthenticationEntryPointHandler();
        return new JwtAuthenticationEntryPoint();
    }

    /**
     * 注冊(cè)  認(rèn)證權(quán)限不足處理 bean
     * @return
     */
    @Bean
    public AccessDeniedHandler myAccessDeniedHandler(){
        return new MyAccessDeniedHandler();
    }

    /**
     * 注冊(cè)  登錄成功 處理 bean
     * @return
     */
    @Bean
    public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
        return new MyAuthenticationSuccessHandler();
    }

    /**
     *  注冊(cè) 登錄失敗 處理 bean
     * @return
     */
    @Bean
    public AuthenticationFailureHandler myAuthenticationFailureHandler(){
        return new MyAuthenticationFailureHandler();
    }

    /**
     * 注冊(cè) 退出系統(tǒng)成功 處理bean
     * @return
     */
    @Bean
    public LogoutSuccessHandler myLogoutSuccessHandler(){
        return new MyLogoutSuccessHandler();
    }

    /**
     * 注冊(cè)jwt 認(rèn)證
     * @return
     * @throws Exception
     */
    @Bean
    public JwtAuthenticationTokenFilter authenticationTokenFilterBean() throws Exception {
        // JwtAuthenticationTokenFilter 過(guò)濾器被配置為跳過(guò)這個(gè)點(diǎn):/auth/v1/api/login/retrieve/pwd 和 /auth/v1/api/login/entry 不進(jìn)行token 驗(yàn)證. 通過(guò) SkipPathRequestMatcher 實(shí)現(xiàn) RequestMatcher 接口來(lái)實(shí)現(xiàn)。
        List<String> pathsToSkip = Arrays.asList("/auth/v1/api/login/retrieve/pwd","/auth/v1/api/login/entry","/auth/v1/api/login/enter");  //不需要token 驗(yàn)證的url
        String processingPath = "/auth/v1/api/**"; // 需要驗(yàn)證token 的url
        SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, processingPath);
        return new JwtAuthenticationTokenFilter(matcher);
    }

    /**
     * 驗(yàn)證登錄驗(yàn)證碼
     * @return
     * @throws Exception
     */
    @Bean
    public UsernamePasswordAuthenticationFilter myUsernamePasswordAuthenticationFilter() throws Exception {
        return new MyUsernamePasswordAuthenticationFilter(authenticationManagerBean(),myAuthenticationSuccessHandler(),myAuthenticationFailureHandler());
    }
}

MyAccessDeniedHandler 自定義權(quán)限不足處理類

/***
 *
 * @FileName: MyAccessDeniedHandler
 * @Company:
 * @author    ljy
 * @Date      2018年05月15日
 * @version   1.0.0
 * @remark:   自定義權(quán)限不足 需要做的業(yè)務(wù)操作
 * @explain   當(dāng)用戶登錄系統(tǒng)后訪問(wèn)資源時(shí)因權(quán)限不足則會(huì)進(jìn)入到此類并執(zhí)行相關(guān)業(yè)務(wù)
 *
 */
@Slf4j
@Component
public class MyAccessDeniedHandler  implements AccessDeniedHandler {

    @Override
    public void handle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AccessDeniedException e) throws IOException, ServletException {
        StringBuffer msg = new StringBuffer("請(qǐng)求: ");
        msg.append(httpServletRequest.getRequestURI()).append(" 權(quán)限不足拐邪,無(wú)法訪問(wèn)系統(tǒng)資源.");
        log.info(msg.toString());
        ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.AUTHORITY,msg.toString());


       /* boolean ajaxRequest = HttpUtils.isAjaxRequest(httpServletRequest);
        if (ajaxRequest){
            //如果是ajax請(qǐng)求 則返回403錯(cuò)
            ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.AUTHORITY,msg.toString());
        }else {
            // 非ajax請(qǐng)求 則跳轉(zhuǎn)到指定的403頁(yè)面
            //此處省略...................
        }*/
    }
}

MyAuthenticationEntryPointHandler 認(rèn)證失敗處理類

/***
 *
 * @FileName: MyAuthenticationEntryPointHandler
 * @Company:
 * @author    ljy
 * @Date      2018年05月15日
 * @version   1.0.0
 * @remark:   認(rèn)證失敗 需要做的業(yè)務(wù)操作
 * @explain   當(dāng)檢測(cè)到用戶訪問(wèn)系統(tǒng)資源認(rèn)證失敗時(shí)則會(huì)進(jìn)入到此類并執(zhí)行相關(guān)業(yè)務(wù)
 *
 */
@Slf4j
@Component
public class MyAuthenticationEntryPointHandler implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        StringBuffer msg = new StringBuffer("請(qǐng)求訪問(wèn): ");
        msg.append(httpServletRequest.getRequestURI()).append(" 接口慰毅, 因?yàn)榈卿洺瑫r(shí),無(wú)法訪問(wèn)系統(tǒng)資源.");
        log.info(msg.toString());
        ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.LOGIN_WITHOUT,msg.toString());

      /*  boolean ajaxRequest = HttpUtils.isAjaxRequest(httpServletRequest);
        if (ajaxRequest){
            //如果是ajax請(qǐng)求 則返回自定義錯(cuò)誤
            ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.LOGIN,map);
        }else {
            // 非ajax請(qǐng)求 則跳轉(zhuǎn)到指定的403頁(yè)面
            //此處省略...................
        }*/
    }
}

MyAuthenticationException 異常

public class MyAuthenticationException extends AuthenticationException {

    public MyAuthenticationException(String msg, Throwable t) {
        super(msg, t);
    }

    public MyAuthenticationException(String msg) {
        super(msg);
    }

    /**
     * 加入錯(cuò)誤狀態(tài)值
     * @param exceptionEnum
     */
    public MyAuthenticationException(ErrorCodeEnum exceptionEnum) {
        super(exceptionEnum.getMessage());
    }

}

MyAuthenticationFailureHandler 登錄失敗處理類

/***
 *
 * @FileName: MyAuthenticationFailureHandler
 * @Company:
 * @author    ljy
 * @Date      2018年05月15日
 * @version   1.0.0
 * @remark:   用戶登錄系統(tǒng)失敗后 需要做的業(yè)務(wù)操作
 * @explain   當(dāng)用戶登錄系統(tǒng)失敗后則會(huì)進(jìn)入到此類并執(zhí)行相關(guān)業(yè)務(wù)
 *
 */
@Slf4j
@Component
public class MyAuthenticationFailureHandler implements AuthenticationFailureHandler {
    @Override
    public void onAuthenticationFailure(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        //用戶登錄時(shí)身份認(rèn)證未通過(guò)
        if (e instanceof BadCredentialsException){
            log.info("用戶登錄時(shí):用戶名或者密碼錯(cuò)誤.");
            ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.LOGIN_INCORRECT);
        }else{
            ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.LOGIN_FAIL);
        }
    }
}

MyAuthenticationSuccessHandler 登錄成功處理類

/***
 *
 * @FileName: MyAuthenticationSuccessHandler
 * @Company:
 * @author    ljy
 * @Date      2018年05月15日
 * @version   1.0.0
 * @remark:   用戶登錄系統(tǒng)成功后 需要做的業(yè)務(wù)操作
 * @explain   當(dāng)用戶登錄系統(tǒng)成功后則會(huì)進(jìn)入到此類并執(zhí)行相關(guān)業(yè)務(wù)
 *
 */
@Slf4j
@Component
public class MyAuthenticationSuccessHandler implements AuthenticationSuccessHandler {

    @Autowired
    private JwtTokenUtil jwtTokenUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Value("${jwt.header}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Value("${jwt.expiration}")
    private Long expiration;

    @Override
    public void onAuthenticationSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //獲得授權(quán)后可得到用戶信息(非jwt 方式)
        //User userDetails =  (User) authentication.getPrincipal();

        //獲得授權(quán)后可得到用戶信息(jwt 方式)
        JWTUserDetails userDetails =  (JWTUserDetails) authentication.getPrincipal();
        //將身份 存儲(chǔ)到SecurityContext里
        SecurityContext securityContext = SecurityContextHolder.getContext();
        securityContext.setAuthentication(authentication);
        httpServletRequest.getSession().setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
        StringBuffer msg = new StringBuffer("用戶:");
        msg.append(userDetails.getUsername()).append(" 成功登錄系統(tǒng).");
        log.info(msg.toString());
        //使用jwt生成token 用于權(quán)限效驗(yàn)
        String token = jwtTokenUtil.generateAccessToken(userDetails);
        UserDetail user = userDetails.getUserInfo();
        user.setToken(token);
        //將登錄人信息放在redis中
        this.saveTokenToRedis(user.getAccountId(),token,JSON.toJSONString(user));
        String access_token = tokenHead+token;
        String refresh_token = tokenHead+jwtTokenUtil.refreshToken(token);
        Map<String,String> map = new HashMap<>();
        map.put("access_token", access_token);
        map.put("refresh_token", refresh_token);
        map.put("userId",user.getAccountId().toString());
        map.put("userName",user.getUserName());
        map.put("email",user.getUserEmail());
        map.put("msage",msg.toString());
        RestfulVo restfulVo = ResultUtil.resultInfo(ErrorCodeEnum.SUCCESS,map);
        ResultUtil.writeJavaScript(httpServletResponse,restfulVo);
    }

    /**
     * 將用戶token 和用戶信息 保存到redis中
     * @param userId  用戶id
     * @param token   用戶token
     * @param value   用戶信息
     */
    private void saveTokenToRedis(Long userId,String token,String value){
        String userKey =  RedisKeys.USER_KEY;
        redisUtil.hset(userKey,token,value,expiration);
    }
}

MyLogoutSuccessHandler 退出成功后處理類

/***
 *
 * @FileName: MyLogoutSuccessHandler
 * @Company:
 * @author    ljy
 * @Date      2018年05月15日
 * @version   1.0.0
 * @remark:   用戶退出系統(tǒng)成功后 需要做的業(yè)務(wù)操作
 * @explain   當(dāng)用戶退出系統(tǒng)成功后則會(huì)進(jìn)入到此類并執(zhí)行相關(guān)業(yè)務(wù)
 *
 */
@Component
public class MyLogoutSuccessHandler implements LogoutSuccessHandler {

    @Autowired
    private RedisUtil redisUtil;
   @Autowired
    private UserUtils userUtils;

    @Override
    public void onLogoutSuccess(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Authentication authentication) throws IOException, ServletException {
        //根據(jù)token清空redis
        String userKey =  RedisKeys.USER_KEY;
        String token = userUtils.getUserToken(httpServletRequest);
        redisUtil.hdel(userKey,token);
        SecurityContextHolder.clearContext();  //清空上下文
        httpServletRequest.getSession().removeAttribute("SPRING_SECURITY_CONTEXT"); // 從session中移除
        //退出信息插入日志記錄表中
        ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.SUCCESS,"退出系統(tǒng)成功.");
    }
}

MyUsernamePasswordAuthenticationFilter 校驗(yàn)驗(yàn)證碼

/***
 *
 * @FileName: MyUsernamePasswordAuthenticationFilter
 * @Company:
 * @author    ljy
 * @Date      2018年07月15日
 * @version   1.0.0
 * @remark:   自定義 登錄校驗(yàn)
 * @explain   調(diào)用登錄接口時(shí)會(huì)進(jìn)入到此類的attemptAuthentication方法 進(jìn)行相關(guān)校驗(yàn)操作
 *
 */
@Slf4j
public class MyUsernamePasswordAuthenticationFilter extends UsernamePasswordAuthenticationFilter {

    public  MyUsernamePasswordAuthenticationFilter(AuthenticationManager authenticationManager,AuthenticationSuccessHandler successHandler,AuthenticationFailureHandler failureHandler){
        this.setFilterProcessesUrl("/auth/v1/api/login/entry");  //這句代碼很重要扎阶,設(shè)置登陸的url 要和 WebSecurityConfig 配置類中的.loginProcessingUrl("/auth/v1/api/login/entry") 一致汹胃,如果不配置則無(wú)法執(zhí)行 重寫(xiě)的attemptAuthentication 方法里面而是執(zhí)行了父類UsernamePasswordAuthenticationFilter的attemptAuthentication()
        this.setAuthenticationManager(authenticationManager);   // AuthenticationManager 是必須的
        this.setAuthenticationSuccessHandler(successHandler);  //設(shè)置自定義登陸成功后的業(yè)務(wù)處理
        this.setAuthenticationFailureHandler(failureHandler); //設(shè)置自定義登陸失敗后的業(yè)務(wù)處理
    }

    @Override
    public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {
        //校驗(yàn)驗(yàn)證碼
        String verifyCode = request.getParameter("verifyCode");
        if(!checkValidateCode(verifyCode)){
            ResultUtil.writeJavaScript(response,ErrorCodeEnum.FAIL,"驗(yàn)證碼錯(cuò)誤.");
            return null;
        }
        //設(shè)置獲取 username 的屬性字段   js傳到后臺(tái)接收數(shù)據(jù)的參數(shù)名
        this.setUsernameParameter("userAccount");
        //設(shè)置獲取password 的屬性字段  js傳到后臺(tái)接收數(shù)據(jù)的參數(shù)名
        this.setPasswordParameter("userPwd");

        return super.attemptAuthentication(request, response);
    }

    /**
     * 驗(yàn)證 驗(yàn)證碼是否正確
     * @param verifyCode
     * @return
     */
    private boolean checkValidateCode(String verifyCode){
        if(StringUtils.isBlank(verifyCode) || !verifyCode.trim().equals("1234")){
          return false;
        }
        return true;
    }
}

整合JWT

JwtTokenUtil jwt 工具類

/***
 *
 * @FileName: JwtTokenUtil
 * @Company:
 * @author    ljy
 * @Date      2018年05月12日
 * @version   1.0.0
 * @remark:   jwt工具類  提供校驗(yàn)toeken 、生成token东臀、根據(jù)token獲取用戶等方法
 *
 */
@Component
public class JwtTokenUtil implements Serializable {

    private static final long serialVersionUID = -5883980282405596071L;

    //
    public static final String ROLE_REFRESH_TOKEN = "ROLE_REFRESH_TOKEN";
    private static final String CLAIM_KEY_USER_ID = "user_id";
    private static final String CLAIM_KEY_AUTHORITIES = "scope";
    private static final String CLAIM_KEY_ACCOUNT_ENABLED = "enabled";
    private static final String CLAIM_KEY_ACCOUNT_NON_LOCKED = "non_locked";
    private static final String CLAIM_KEY_ACCOUNT_NON_EXPIRED = "non_expired";
    private static final String CLAIM_KEY_USER_ACCOUNT = "sub";
    private static final String CLAIM_KEY_CREATED = "created";
    //簽名方式
    private final SignatureAlgorithm SIGNATURE_ALGORITHM = SignatureAlgorithm.HS256;

    //密匙
    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.access_token}")
    private Long access_token_expiration;

    @Value("${jwt.refresh_token}")
    private Long refresh_token_expiration;
    //過(guò)期時(shí)間
    @Value("${jwt.expiration}")
    private Long expiration;

    /**
     * 根據(jù)token 獲取用戶信息
     * @param token
     * @return
     */
    public JWTUserDetails getUserFromToken(String token) {
        JWTUserDetails  jwtUserDetails;
        try {
            final Claims claims = getClaimsFromToken(token);
            long userId = getUserIdFromToken(token);
            String username = claims.getSubject();
            List<?> roles = (List<?>) claims.get(CLAIM_KEY_AUTHORITIES);
            Collection<? extends GrantedAuthority> authorities = parseArrayToAuthorities(roles);
            boolean account_enabled = (Boolean) claims.get(CLAIM_KEY_ACCOUNT_ENABLED);
            boolean account_non_locked = (Boolean) claims.get(CLAIM_KEY_ACCOUNT_NON_LOCKED);
            boolean account_non_expired = (Boolean) claims.get(CLAIM_KEY_ACCOUNT_NON_EXPIRED);
            User user = new User(username, "", account_enabled, account_non_expired, account_non_expired, account_non_locked, authorities);

            jwtUserDetails = JWTUserDetailsFactory.create(user, userId,Instant.now());
        } catch (Exception e) {
            jwtUserDetails = null;
        }
        return jwtUserDetails;
    }

    /**
     * 根據(jù)token 獲取用戶ID
     * @param token
     * @return
     */
    public long getUserIdFromToken(String token) {
        long userId;
        try {
            final Claims claims = getClaimsFromToken(token);
            userId = Long.valueOf(claims != null ? claims.get(CLAIM_KEY_USER_ID).toString() :"0");
        } catch (Exception e) {
            e.printStackTrace();
            userId = 0;
        }
        return userId;
    }

    /**
     * 根據(jù)token 獲取用戶名
     * @param token
     * @return
     */
    public String getUsernameFromToken(String token) {
        String username;
        try {
            final Claims claims = getClaimsFromToken(token);
            username = claims.getSubject();
        } catch (Exception e) {
            username = null;
        }
        return username;
    }

    /**
     * 根據(jù)token 獲取生成時(shí)間
     * @param token
     * @return
     */
    public Date getCreatedDateFromToken(String token) {
        Date created;
        try {
            final Claims claims = getClaimsFromToken(token);
            created = claims.getIssuedAt();
        } catch (Exception e) {
            created = null;
        }
        return created;
    }

    /**
     * 根據(jù)token 獲取過(guò)期時(shí)間
     * @param token
     * @return
     */
    public Date getExpirationDateFromToken(String token) {
        Date expiration;
        try {
            final Claims claims = getClaimsFromToken(token);
            expiration = claims.getExpiration();
        } catch (Exception e) {
            expiration = null;
        }
        return expiration;
    }

    /***
     * 解析token 信息
     * @param token
     * @return
     */
    private Claims getClaimsFromToken(String token) {
        Claims claims;
        try {
            claims = Jwts.parser()
                    .setSigningKey(secret)   //簽名的key
                    .parseClaimsJws(token)   // 簽名token
                    .getBody();
        } catch (Exception e) {
            claims = null;
        }
        return claims;
    }

    /**
     * 生成失效時(shí)間
     * @param expiration
     * @return
     */
    private Date generateExpirationDate(long expiration) {
        return new Date(System.currentTimeMillis() + expiration * 1000);
    }

    /**
     * token 是否過(guò)期
     * @param token
     * @return
     */
    private Boolean isTokenExpired(String token) {
        final Date expiration = getExpirationDateFromToken(token);
        return expiration.before(new Date());
    }

    /**
     * 生成時(shí)間是否在最后修改時(shí)間之前
     * @param created   生成時(shí)間
     * @param lastPasswordReset  最后修改密碼時(shí)間
     * @return
     */
    private Boolean isCreatedBeforeLastPasswordReset(Date created, Date lastPasswordReset) {
        return (lastPasswordReset != null && created.before(lastPasswordReset));
    }

    /**
     * 根據(jù)用戶信息 生成token
     * @param userDetails
     * @return
     */
    public String generateAccessToken(UserDetails userDetails) {
        JWTUserDetails user = (JWTUserDetails) userDetails;
        Map<String, Object> claims = generateClaims(user);
        claims.put(CLAIM_KEY_AUTHORITIES, JSON.toJSON(authoritiesToArray(user.getAuthorities())));
        return generateAccessToken(user.getUsername(), claims);
    }

    /**
     * 重置(更新)token 過(guò)期時(shí)間
     * @param token
     * @param expiration
     */
    public String restTokenExpired(String token,long expiration){

        final Claims claims = getClaimsFromToken(token);
        Jwts.builder()
                .setClaims(claims)   //一個(gè)map 可以資源存放東西進(jìn)去
                .setSubject(claims.getSubject()) //  用戶名寫(xiě)入標(biāo)題
                .setExpiration(new Date(expiration));
        //claims.setExpiration(new Date(expiration));
        // String refreshedToken = generateAccessToken(claims.getSubject(), claims,expiration);
        return "";
    }

    private Map<String, Object> generateClaims(JWTUserDetails user) {
        Map<String, Object> claims = new HashMap<>();
        claims.put(CLAIM_KEY_USER_ID, user.getUserId());
        claims.put(CLAIM_KEY_ACCOUNT_ENABLED, user.isEnabled());
        claims.put(CLAIM_KEY_ACCOUNT_NON_LOCKED, user.isAccountNonLocked());
        claims.put(CLAIM_KEY_ACCOUNT_NON_EXPIRED, user.isAccountNonExpired());
        return claims;
    }

    /**
     * 生成token
     * @param subject  用戶名
     * @param claims
     * @return
     */
    private String generateAccessToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, access_token_expiration);
    }


    /**
     * 生成token
     * @param subject  用戶名
     * @param claims
     * @return
     */
    private String generateAccessToken(String subject, Map<String, Object> claims,long expiration) {
        return generateToken(subject, claims, expiration);
    }

    /**
     * 用戶所擁有的資源權(quán)限
     * @param authorities
     * @return
     */
    private List<?> authoritiesToArray(Collection<? extends GrantedAuthority> authorities) {
        List<String> list = new ArrayList<>();
        for (GrantedAuthority ga : authorities) {
            list.add(ga.getAuthority());
        }
        return list;
    }

    private Collection<? extends GrantedAuthority> parseArrayToAuthorities(List<?> roles) {
        Collection<GrantedAuthority> authorities = new ArrayList<>();
        SimpleGrantedAuthority authority;
        for (Object role : roles) {
            authority = new SimpleGrantedAuthority(role.toString());
            authorities.add(authority);
        }
        return authorities;
    }

    /**
     * 根據(jù)用戶信息 重新獲取token
     * @param userDetails
     * @return
     */
    public String generateRefreshToken(UserDetails userDetails) {
        JWTUserDetails user = (JWTUserDetails) userDetails;
        Map<String, Object> claims = generateClaims(user);
        // 只授于更新 token 的權(quán)限
        String roles[] = new String[]{ROLE_REFRESH_TOKEN};
        claims.put(CLAIM_KEY_AUTHORITIES, JSON.toJSON(roles));
        return generateRefreshToken(user.getUsername(), claims);
    }

    /**
     * 重新獲取token
     * @param subject 用戶名
     * @param claims
     * @return
     */
    private String generateRefreshToken(String subject, Map<String, Object> claims) {
        return generateToken(subject, claims, refresh_token_expiration);
    }

    public Boolean canTokenBeRefreshed(String token, Date lastPasswordReset) {
        final Date created = getCreatedDateFromToken(token);
        return !isCreatedBeforeLastPasswordReset(created, lastPasswordReset)
                && (!isTokenExpired(token));
    }

    /**
     * 刷新重新獲取token
     * @param token 源token
     * @return
     */
    public String refreshToken(String token) {
        String refreshedToken;
        try {
            final Claims claims = getClaimsFromToken(token);
            refreshedToken = generateAccessToken(claims.getSubject(), claims);
        } catch (Exception e) {
            refreshedToken = null;
        }
        return refreshedToken;
    }

    private String generateToken(String subject, Map<String, Object> claims, long expiration) {
        return Jwts.builder()
                .setClaims(claims)   //一個(gè)map 可以資源存放東西進(jìn)去
                .setSubject(subject) //  用戶名寫(xiě)入標(biāo)題
                .setId(UUID.randomUUID().toString())
                .setIssuedAt(new Date())
                .setExpiration(generateExpirationDate(expiration))  //過(guò)期時(shí)間
                //.setNotBefore(now)              //系統(tǒng)時(shí)間之前的token都是不可以被承認(rèn)的
                .signWith(SIGNATURE_ALGORITHM, secret) //數(shù)字簽名
                .compact();
    }

    /**
     * 驗(yàn)證token 是否合法
     * @param token  token
     * @param userDetails  用戶信息
     * @return
     */
    public Boolean validateToken(String token, UserDetails userDetails) {
        JWTUserDetails user = (JWTUserDetails) userDetails;
        final long userId = getUserIdFromToken(token);
        final String username = getUsernameFromToken(token);
        // final Date created = getCreatedDateFromToken(token);
        // final Date expiration = getExpirationDateFromToken(token);
        return (userId == user.getUserId()
                && username.equals(user.getUsername())
                && !isTokenExpired(token)
                /* && !isCreatedBeforeLastPasswordReset(created, userDetails.getLastPasswordResetDate()) */
        );
    }


}

JWTUserDetails 用戶信息

/***
 *
 * @FileName: JWTUserDetails
 * @Company:
 * @author    ljy
 * @Date      2018年05月120日
 * @version   1.0.0
 * @remark:   jwt用戶信息
 * @explain   Spring Security需要我們實(shí)現(xiàn)幾個(gè)東西着饥,第一個(gè)是UserDetails:這個(gè)接口中規(guī)定了用戶的幾個(gè)必須要有的方法,所以我們創(chuàng)建一個(gè)JwtUser類來(lái)實(shí)現(xiàn)這個(gè)接口惰赋。為什么不直接使用User類宰掉?因?yàn)檫@個(gè)UserDetails完全是為了安全服務(wù)的,它和我們的領(lǐng)域類可能有部分屬性重疊赁濒,但很多的接口其實(shí)是安全定制的轨奄,所以最好新建一個(gè)類:
 *
 */
public class JWTUserDetails implements UserDetails {

    private Long userId;         //用戶ID
    private String password;       //用戶密碼
    private final String username; //用戶名
    private final Collection<? extends GrantedAuthority> authorities;  //用戶角色權(quán)限
    private final Boolean isAccountNonExpired;       //賬號(hào)是否過(guò)期
    private final Boolean isAccountNonLocked;        //賬戶是否鎖定
    private final Boolean isCredentialsNonExpired;   //密碼是否過(guò)期
    private  Boolean enabled;                   //是否激活
    private final Instant lastPasswordResetDate;        //上次密碼重置時(shí)間
    private UserDetail userInfo;

    public JWTUserDetails(Long userId, String username, String password, Collection<? extends GrantedAuthority> authorities,Instant lastPasswordResetDate) {
        this(userId, username, password, true, true, true, true, authorities,lastPasswordResetDate);
    }

    public JWTUserDetails(UserDetail userInfo, Collection<? extends GrantedAuthority> authorities) {
        this.userInfo = userInfo;
        if (userInfo != null && StringUtils.isNotBlank(userInfo.getUserAccount())) {
            this.userId = userInfo.getAccountId();
            this.username = userInfo.getUserAccount();
            this.password = userInfo.getUserPwd();
            this.enabled = userInfo.getStatus() == 0 ? false : true;
            this.isAccountNonExpired = true;
            this.isAccountNonLocked = true;
            this.isCredentialsNonExpired = true;
            this.authorities = authorities;
            this.lastPasswordResetDate = userInfo.getLastPasswordResetDate();
        } else {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }

    public JWTUserDetails(Long userId, String username, String password, boolean enabled, boolean isAccountNonExpired, boolean isCredentialsNonExpired, boolean isAccountNonLocked, Collection<? extends GrantedAuthority> authorities,Instant lastPasswordResetDate) {
        if (username != null && !"".equals(username) && password != null) {
            this.userId = userId;
            this.username = username;
            this.password = password;
            this.enabled = enabled;
            this.isAccountNonExpired = isAccountNonExpired;
            this.isAccountNonLocked = isAccountNonLocked;
            this.isCredentialsNonExpired = isCredentialsNonExpired;
            this.authorities = authorities;
            this.lastPasswordResetDate = lastPasswordResetDate;
        } else {
            throw new IllegalArgumentException("Cannot pass null or empty values to constructor");
        }
    }


    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return authorities;
    }

    @JsonIgnore
    @Override
    public String getPassword() {
        return password;
    }


    @Override
    public String getUsername() {
        return username;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonExpired() {
        return isAccountNonExpired;
    }

    @JsonIgnore
    @Override
    public boolean isAccountNonLocked() {
        return isAccountNonLocked;
    }

    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired() {
        return isCredentialsNonExpired;
    }

    @JsonIgnore
    @Override
    public boolean isEnabled() {
        return enabled;
    }

    @JsonIgnore
    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    @JsonIgnore
    public Instant getLastPasswordResetDate() {
        return lastPasswordResetDate;
    }

    public UserDetail getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(UserDetail userInfo) {
        this.userInfo = userInfo;
    }
}

JWTUserDetailsFactory

/***
 *
 * @FileName: JWTUserDetailsFactory
 * @Company:
 * @author    ljy
 * @Date      2018年05月120日
 * @version   1.0.0
 * @remark:   負(fù)責(zé)創(chuàng)建JWTUserDetails 對(duì)象
 *
 */
public final class JWTUserDetailsFactory {

    private JWTUserDetailsFactory(){

    }

    public static JWTUserDetails create(User user, Long userId, Instant date){
        return new JWTUserDetails(userId, user.getUsername(), user.getPassword(),user.getAuthorities(), date);
    }

    public static JWTUserDetails create(User user, UserDetail userDetail){
        return new JWTUserDetails(userDetail,user.getAuthorities());
    }

    private static List<GrantedAuthority> mapToGrantedAuthorities(List<String> authorities) {
        return authorities.stream()
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

}

JwtAuthenticationTokenFilter 請(qǐng)求接口時(shí)校驗(yàn)token信息

/***
 *
 * @FileName: JwtAuthenticationTokenFilter
 * @Company:
 * @author    ljy
 * @Date      2018年05月120日
 * @version   1.0.0
 * @remark:   jwt認(rèn)證token
 * @explain   每次請(qǐng)求接口時(shí) 就會(huì)進(jìn)入這里驗(yàn)證token 是否合法
 *             token 如果用戶一直在操作,則token 過(guò)期時(shí)間會(huì)疊加    如果超過(guò)設(shè)置的過(guò)期時(shí)間未操作  則token 失效 需要重新登錄
 *
 */
@Slf4j
@Component
public class JwtAuthenticationTokenFilter extends OncePerRequestFilter {


    @Resource(name = "myUserDetailService")
    private UserDetailsService userDetailsService;

    @Autowired
    private RedisUtil redisUtil;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Value("${jwt.header}")
    private String tokenHeader;

    @Value("${jwt.tokenHead}")
    private String tokenHead;

    @Value("${jwt.expiration}")
    private Long expiration;

    private RequestMatcher authenticationRequestMatcher;

    public JwtAuthenticationTokenFilter() {

    }

    public JwtAuthenticationTokenFilter(RequestMatcher authenticationRequestMatcher) {
        this.authenticationRequestMatcher = authenticationRequestMatcher;
    }


    @Override
    protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException {
        //過(guò)濾掉不需要token驗(yàn)證的url
        if(authenticationRequestMatcher != null && !authenticationRequestMatcher.matches(httpServletRequest)){
            filterChain.doFilter(httpServletRequest, httpServletResponse);
            return;
        }
        String authHeader = httpServletRequest.getHeader(this.tokenHeader);
        if (authHeader != null && authHeader.startsWith(tokenHead)) {
            final String authToken = authHeader.substring(tokenHead.length()); // The part after "Bearer "
            log.info("請(qǐng)求"+httpServletRequest.getRequestURI()+"攜帶的token值:" + authToken);
            // 查看redis中的token信息是否過(guò)期
            boolean isExists = redisUtil.hexists(RedisKeys.USER_KEY,authToken);
            if (!isExists){
                // token 過(guò)期 提示用戶登錄超時(shí) 重新登錄系統(tǒng)
                //throw  new MyAuthenticationException(ErrorCodeEnum.LOGIN_WITHOUT);
            }

            //如果在token過(guò)期之前觸發(fā)接口,我們更新token過(guò)期時(shí)間拒炎,token值不變只更新過(guò)期時(shí)間
            Date createTokenDate = jwtTokenUtil.getCreatedDateFromToken(authToken);  //獲取token生成時(shí)間
            log.info("createTokenDate: " + createTokenDate);
            if(createTokenDate != null){
                Duration between = Duration.between(Instant.now(), Instant.ofEpochMilli(createTokenDate.getTime()));
                Long differSeconds = between.toMillis();
                boolean isExpire = expiration > differSeconds;
                if (isExpire) {  //如果 請(qǐng)求接口時(shí)間在token 過(guò)期之前 則更新token過(guò)期時(shí)間  我們可以將用戶的token 存放到redis 中,更新redis 的過(guò)期時(shí)間

                    //更新 延長(zhǎng) redis 中的過(guò)期時(shí)間


                }
            }


            String useraccount = jwtTokenUtil.getUsernameFromToken(authToken);
            log.info("JwtAuthenticationTokenFilter[doFilterInternal] checking authentication " + useraccount);

            //token校驗(yàn)通過(guò)
            if(useraccount != null){
                TokenUtils.setToken(authToken);//設(shè)置token
                Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
                if(authentication == null  || authentication.getPrincipal().equals("anonymousUser")){
                    //根據(jù)account去數(shù)據(jù)庫(kù)中查詢user數(shù)據(jù)挪拟,足夠信任token的情況下,可以省略這一步
                    UserDetails userDetails = this.userDetailsService.loadUserByUsername(useraccount);

                    if (jwtTokenUtil.validateToken(authToken, userDetails)) {
                        UsernamePasswordAuthenticationToken usernamePasswordAuthentication = new UsernamePasswordAuthenticationToken(
                                userDetails, null, userDetails.getAuthorities());
                        usernamePasswordAuthentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(
                                httpServletRequest));
                        log.info("JwtAuthenticationTokenFilter[doFilterInternal]  authenticated user " + useraccount + ", setting security context");
                        SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthentication);
                    }
                }else {
                    log.info("當(dāng)前請(qǐng)求用戶信息:"+ JSON.toJSONString(authentication.getPrincipal()));
                }
            }else {
                log.info("token 無(wú)效.");
                throw  new MyAuthenticationException(ErrorCodeEnum.TOKEN_INVALID);
            }
        }
        filterChain.doFilter(httpServletRequest, httpServletResponse);
    }
}

JwtAuthenticationEntryPoint jwt認(rèn)證失敗處理

/***
 *
 * @FileName: JwtAuthenticationEntryPoint
 * @Company:
 * @author    ljy
 * @Date      2018年05月120日
 * @version   1.0.0
 * @remark:   jwt 認(rèn)證處理類
 *
 */
@Slf4j
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {

    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        StringBuffer msg = new StringBuffer("請(qǐng)求訪問(wèn): ");
        msg.append(httpServletRequest.getRequestURI()).append(" 接口击你, 經(jīng)jwt 認(rèn)證失敗玉组,無(wú)法訪問(wèn)系統(tǒng)資源.");
        log.info(msg.toString());
        log.info(e.toString());
        // 用戶登錄時(shí)身份認(rèn)證未通過(guò)
        if(e instanceof BadCredentialsException) {
            log.info("用戶登錄時(shí)身份認(rèn)證失敗.");
            ResultUtil.writeJavaScript(httpServletResponse, ErrorCodeEnum.LOGIN_INCORRECT, msg.toString());
        }else if(e instanceof InsufficientAuthenticationException){
            log.info("缺少請(qǐng)求頭參數(shù),Authorization傳遞是token值所以參數(shù)是必須的.");
            ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.NO_TOKEN,msg.toString());
        }else {
            log.info("用戶token無(wú)效.");
            ResultUtil.writeJavaScript(httpServletResponse,ErrorCodeEnum.TOKEN_INVALID,msg.toString());
        }

    }
}

工具類

SkipPathRequestMatcher 忽略不進(jìn)行token校驗(yàn)的url

public class SkipPathRequestMatcher implements RequestMatcher {
    private OrRequestMatcher matchers;
    private RequestMatcher processingMatcher;

    public SkipPathRequestMatcher(List<String> pathsToSkip, String processingPath) {
        List<RequestMatcher> m = pathsToSkip.stream().map(path -> new AntPathRequestMatcher(path)).collect(Collectors.toList());
        matchers = new OrRequestMatcher(m);
        processingMatcher = new AntPathRequestMatcher(processingPath);
    }

    @Override
    public boolean matches(HttpServletRequest request) {
        if (matchers.matches(request)) {
            return false;
        }
        return processingMatcher.matches(request) ? true : false;
    }
}

UrlMatcher url匹配接口

public interface UrlMatcher {
    Object compile(String paramString);
    boolean pathMatchesUrl(Object paramObject, String paramString);
    String getUniversalMatchPattern();
    boolean requiresLowerCaseUrl();
}

AntUrlPathMatcher url 匹配 在 MyInvocationSecurityMetadataSourceService 類中會(huì)用到

@Component
public class AntUrlPathMatcher implements UrlMatcher {
    private boolean requiresLowerCaseUrl;
    private PathMatcher pathMatcher;
    public AntUrlPathMatcher()   {
        this(true);

    }
    public AntUrlPathMatcher(boolean requiresLowerCaseUrl)
    {
        this.requiresLowerCaseUrl = true;
        this.pathMatcher = new AntPathMatcher();
        this.requiresLowerCaseUrl = requiresLowerCaseUrl;
    }

    public Object compile(String path) {
        if (this.requiresLowerCaseUrl) {
            return path.toLowerCase();
        }
        return path;
    }

    public void setRequiresLowerCaseUrl(boolean requiresLowerCaseUrl){

        this.requiresLowerCaseUrl = requiresLowerCaseUrl;
    }

    public boolean pathMatchesUrl(Object path, String url) {
        if (("/**".equals(path)) || ("**".equals(path))) {
            return true;
        }

        return this.pathMatcher.match((String)path, url);
    }

    public String getUniversalMatchPattern() {
        return"/**";
    }

    public boolean requiresLowerCaseUrl() {
        return this.requiresLowerCaseUrl;
    }

    public String toString() {
        return super.getClass().getName() + "[requiresLowerCase='"
                + this.requiresLowerCaseUrl + "']";
    }

前端使用ajax方式登錄

 //登錄頁(yè)面登錄按鈕事件
    var handleSignInFormSubmit = function() {
        $('#m_login_signin_submit').click(function(e) {
            e.preventDefault();
            var btn = $(this);
            var form = $(this).closest('form');

            form.validate({
                rules: {
                    userAccount: {
                        required: true
                    },
                    userPwd: {
                        required: true
                    }
                },
                messages: {
                        userAccount: {
                                required: "請(qǐng)輸入登錄用戶."
                        },
                        userPwd: {
                                required: "請(qǐng)輸入登錄密碼."
                        }
                    }
            });

            if (!form.valid()) {
                return;
            }

            btn.addClass('m-loader m-loader--right m-loader--light').attr('disabled', true);

            form.ajaxSubmit({
                type: 'post',
                url: ajaxUrl+'login/entry',
                success: function(response, status, xhr, $form) {
                    console.log(response);
                    if(response.status != 0){
                        btn.removeClass('m-loader m-loader--right m-loader--light').attr('disabled', false);
                        showErrorMsg(form, 'danger', '錯(cuò)誤的用戶名或密碼.');
                    }else {
                        //得到登錄后的token
                        var access_token = response.data.access_token;
                        localStorage.setItem('user_token', JSON.stringify(access_token));
                        localStorage.setItem('user',  JSON.stringify(response.data));
                        window.location.href="assets/snippets/pages/home/index.html";
                    }
                },
                error:function (response, status, xhr) {
                    btn.removeClass('m-loader m-loader--right m-loader--light').attr('disabled', false);
                    showErrorMsg(form, 'danger', '網(wǎng)絡(luò)出現(xiàn)錯(cuò)誤.');
                }
            });
            return false; // 阻止表單自動(dòng)提交事件,必須返回false丁侄,否則表單會(huì)自己再做一次提交操作惯雳,并且頁(yè)面跳轉(zhuǎn)
        });
    }

攜帶token信息請(qǐng)求其它接口獲取數(shù)據(jù)(以刪除為例)

  var userToken = JSON.parse(localStorage.getItem('user_token'));

  $.ajax({
                url: ajaxUrl+'userRole/del',
                data : {
                    roleId:checkboxRoleId,
                    userIds:selectDeleteUserIds.toString(),
                    _method: 'DELETE'
                },
                type:"POST",
                dataType:"json",
                headers: {'Authorization': userToken},  //攜帶token到后臺(tái)的方式
                success: function(response, status, xhr) {
                    var serverStatus = response.status;
                    toastr.clear();
                    //這里根據(jù)后臺(tái)返回的狀態(tài)做相應(yīng)的處理,后臺(tái)會(huì)根據(jù)token去校驗(yàn)權(quán)限鸿摇,并返回相應(yīng)的響應(yīng)
                    if(serverStatus == -1){  
                        //登陸超時(shí)吨凑,需要重新登陸系統(tǒng)
                        toastr.error(response.message);
                        toastr.info("即將跳轉(zhuǎn)到登陸頁(yè)面.");
                        window.location.href="../../../../login.html";
                    }else  if (response.status != 0){
                        // 我設(shè)置的 response.status == 0 表示成功
                       //  response.status != 0 有可能是 無(wú)權(quán)限訪問(wèn)、token錯(cuò)誤户辱、登錄失效等 根據(jù)后臺(tái)認(rèn)證返回 
                        toastr.error(response.message);
                    }else {
                        reloadUserGrid();
                    }
                    //移除遮罩層
                    mApp.unblock('#be-authorized-userGrid');
                },
                error:function (response, status, xhr) {
                    toastr.error("網(wǎng)絡(luò)出現(xiàn)錯(cuò)誤.");
                }
            });
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市糙臼,隨后出現(xiàn)的幾起案子庐镐,更是在濱河造成了極大的恐慌,老刑警劉巖变逃,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件必逆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)名眉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)粟矿,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人损拢,你說(shuō)我怎么就攤上這事陌粹。” “怎么了福压?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵掏秩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我荆姆,道長(zhǎng)蒙幻,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任胆筒,我火速辦了婚禮邮破,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘仆救。我一直安慰自己抒和,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布派桩。 她就那樣靜靜地躺著构诚,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铆惑。 梳的紋絲不亂的頭發(fā)上范嘱,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天,我揣著相機(jī)與錄音员魏,去河邊找鬼丑蛤。 笑死,一個(gè)胖子當(dāng)著我的面吹牛撕阎,可吹牛的內(nèi)容都是我干的受裹。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼虏束,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼棉饶!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起镇匀,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤照藻,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后汗侵,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體幸缕,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡群发,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了发乔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片熟妓。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖栏尚,靈堂內(nèi)的尸體忽然破棺而出起愈,到底是詐尸還是另有隱情,我是刑警寧澤抵栈,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布告材,位于F島的核電站,受9級(jí)特大地震影響古劲,放射性物質(zhì)發(fā)生泄漏斥赋。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一产艾、第九天 我趴在偏房一處隱蔽的房頂上張望疤剑。 院中可真熱鬧,春花似錦闷堡、人聲如沸隘膘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)弯菊。三九已至,卻和暖如春踱阿,著一層夾襖步出監(jiān)牢的瞬間管钳,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工软舌, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留才漆,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓佛点,卻偏偏與公主長(zhǎng)得像醇滥,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子超营,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容