springboot+shiro使用jwt实现权限URL细粒度控制

最近在使用jwt作为token使用,然后整合shiro,并实现URL权限细粒度控制。本项目只是一个demo。可能有些考虑的不太全

对于shiro控制权限的一些注解我个人觉得有些麻烦,需要在每个接口加上注解,不太灵活,而且我要控制URL级别的权限

比如A用户 拥有GET方式 的/sysUser/**和/sys/** 权限,那么A用户就只能去访问/sysUser/xxxx和/sys/xxxx下面的接口,其它接口就不能访问。

技术框架:springboot+mybatis-plus+shiro 

首先是登录流程:

当用户输入账号和密码后,调登录接口(登录接口不需要鉴权,放行),后台查询用户和匹配密码成功后,返回一个jwt 的token(包含userId的字段)。然后前端拿到这个token放到header中,下次调用接口需要带上token。

权限验证:

当访问某个接口需要鉴权时,会被拦截类JWTFilter拦截,然后验证token的合法性,查询数据库该用户有哪些权限,然后在比对用户是否有这个接口的权限,有则放行,无则授权失败,禁止访问。

写demo时候遇到了很多问题

1.token过期了怎么解决,怎么续期?

由于jwt的特性将过期时间写在了token里 ,我暂时无法完美解决这个问题,查找了很多文章还是没办法,迫于无奈还是用了redis

暂时的方法是,登录成功后将token存入redis,并设置过期时间为token失效时间的2倍,比如token失效时间是30分钟,则存入的redis中的token失效时间就是60分钟。当token验证抛出过期异常时,就查询redis中是否存在为key的token,如果不存在就是真的过期,需要用户重新登录,如果redis中查询到了token,就生成的新的token给前端,然后删除redis中的旧token,就相当于续期了。

当然关于并发刷新token的问题可以参考这个https://www.sundayfine.com/jwt-refresh-token/,引入Redis锁机制来控制的.

2.拦截器抛异常自定义全局异常捕获不到的问题

重写下面的方法,相当于springboot的全局异常处理

package com.pwl.shiro.controller;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Pan Weilong
 * @date 2019/6/25 21:55
 * @description: 处理filter抛出的异常,相当于springboot全局异常
 */
@RestControllerAdvice
public class ExceptionController extends BasicErrorController {

    public ExceptionController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }
    public ExceptionController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        super(errorAttributes, errorProperties);
    }

    public ExceptionController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List errorViewResolvers) {
        super(errorAttributes, errorProperties, errorViewResolvers);
    }

    /**
     * @Author Pan Weilong
     * @Description 处理传过来的异常(解决自定义全局异常捕获不到的问题)
     * @Date 17:16 2019/6/27
     * @Param [request]
     * @return org.springframework.http.ResponseEntity>
     **/
    @Override
    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity> error(HttpServletRequest request) {
        Map body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        String code=body.get("status").toString();
        String message = body.get("message").toString();
        Map map = new HashMap();
        map.put("code",code);
        map.put("msg",message);
        return new ResponseEntity>(map, status);
    }
}

贴部分代码

项目结构

springboot+shiro使用jwt实现权限URL细粒度控制_第1张图片

首先pom:



    4.0.0
    
        org.springframework.boot
        spring-boot-starter-parent
        2.1.6.RELEASE
         
    
    com.pwl
    springboot-shiro-jwt
    0.0.1-SNAPSHOT
    springboot-shiro-jwt
    Demo project for Spring Boot

    
        1.8
    

    
        
            org.springframework.boot
            spring-boot-starter-web
        

        
            org.springframework.boot
            spring-boot-starter-test
            test
        

        
        
            org.apache.shiro
            shiro-spring
            1.3.2
        
        
            com.auth0
            java-jwt
            3.2.0
        

        
        
            mysql
            mysql-connector-java
        
        
        
            com.baomidou
            mybatis-plus-boot-starter
            2.2.0
        

        
        
            com.zaxxer
            HikariCP
        

        
            org.apache.velocity
            velocity-engine-core
            2.0
        

        
        
            org.springframework.boot
            spring-boot-starter-data-redis
        
    

    
        
            
                org.springframework.boot
                spring-boot-maven-plugin
            
        
    


自定义异常类

package com.pwl.shiro.exception;

import com.pwl.shiro.common.ResultVO;
import org.apache.shiro.authz.UnauthenticatedException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

import javax.servlet.http.HttpServletRequest;

/**
 * @Author Pan Weilong
 * @Description 全局异常捕获
 * @Date 15:11 2019/6/20
 * @Param
 * @return
 **/
@ControllerAdvice
public class GlobalExceptionHandler implements ApplicationContextAware {

    private Logger logger = LoggerFactory.getLogger(GlobalExceptionHandler.class);

    private ApplicationContext applicationContext;

    @ExceptionHandler(value = Exception.class)
    @ResponseBody
    public ResultVO defaultErrorHandler(HttpServletRequest request, Exception e) throws Exception {
        if(e instanceof UnauthenticatedException){
            return new ResultVO().returnFail("无认证");
        }
        return new ResultVO().returnFail(e.getMessage());
    }

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
		this.applicationContext = applicationContext;
	}
}

然后是配置文件

spring:
  datasource:
    type: com.zaxxer.hikari.HikariDataSource
    driver-class-name: com.mysql.cj.jdbc.Driver
    url: jdbc:mysql://127.0.0.1:3306/shiro?characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false
    username: root
    password: root
    hikari:
      connection-test-query: SELECT 1 FROM DUAL
      minimum-idle: 1
      maximum-pool-size: 20
      pool-name: bosPoolName
      max-lifetime: 1800000
  redis:
    database: 0
    host: 127.0.0.1
    port: 6379
    #password:

#mybaits-plus配置,修改主键类型,mapper.xml、type 别名等
mybatis-plus:
  mapper-locations: classpath:/mapper/*Mapper.xml
  typeAliasesPackage: com.pwl.shiro.entity
  global-config:
    #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 0
    #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
    field-strategy: 1
    #驼峰下划线转换
    db-column-underline: true
    #刷新mapper 调试神器
    refresh-mapper: true
    #数据库大写下划线转换
    #capital-mode: true
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: true
logging:
  level:
    com:
      pwl:
        shiro: debug
security:
  secret: token  #秘钥

token:
  tokenExpireTime: 30 #令牌有效期分钟


 

shiro配置类(这个配置很重要

package com.pwl.shiro.config;

import com.pwl.shiro.filter.JWTFilter;
import com.pwl.shiro.ream.UrlPermissionResolver;
import com.pwl.shiro.ream.UserRealm;
import com.pwl.shiro.util.JwtProperties;
import org.apache.shiro.mgt.DefaultSessionStorageEvaluator;
import org.apache.shiro.mgt.DefaultSubjectDAO;
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.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.DependsOn;
import org.springframework.data.redis.core.StringRedisTemplate;

import javax.servlet.Filter;
import java.util.HashMap;
import java.util.Map;

/**
 * @author Pan Weilong
 * @date 2019/6/24 22:19
 * @description: 接口.
 */
@Configuration
public class ShiroConfig {

    @Bean
    public UserRealm userRealm(){
        UserRealm userRealm = new UserRealm();
        userRealm.setPermissionResolver(new UrlPermissionResolver());
        return userRealm;
    }

    @Bean("securityManager")
    public DefaultWebSecurityManager getManager(UserRealm userRealm) {
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        // 使用自己的realm
        manager.setRealm(userRealm);

        /*
         * 关闭shiro自带的session,详情见文档
         * http://shiro.apache.org/session-management.html#SessionManagement-StatelessApplications%28Sessionless%29
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        manager.setSubjectDAO(subjectDAO);

        return manager;
    }


    @Bean("shiroFilter")
    public ShiroFilterFactoryBean factory(DefaultWebSecurityManager securityManager, StringRedisTemplate stringRedisTemplate,JwtProperties jwtProp) {
        ShiroFilterFactoryBean factoryBean = new ShiroFilterFactoryBean();

        // 添加自己的过滤器并且取名为jwt
        Map filterMap = new HashMap<>();
        filterMap.put("jwt", new JWTFilter(jwtProp.getSecret(),jwtProp.getTokenExpireTime(),stringRedisTemplate));
        factoryBean.setFilters(filterMap);

        factoryBean.setSecurityManager(securityManager);

        /*
         * 自定义url规则
         * http://shiro.apache.org/web.html#urls-
         */
        Map filterRuleMap = new HashMap<>();
        // 所有请求通过我们自己的JWT Filter
        filterRuleMap.put("/login", "anon");
        filterRuleMap.put("/**", "jwt");
        // 访问401和404页面不通过我们的Filter
        filterRuleMap.put("/401", "anon");
        factoryBean.setFilterChainDefinitionMap(filterRuleMap);
        return factoryBean;
    }

    /**
     * 下面的代码是添加注解支持
     */
    @Bean
    @DependsOn("lifecycleBeanPostProcessor")
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() {
        DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();
        // 强制使用cglib,防止重复代理和可能引起代理出错的问题
        // https://zhuanlan.zhihu.com/p/29161098
        defaultAdvisorAutoProxyCreator.setProxyTargetClass(true);
        return defaultAdvisorAutoProxyCreator;
    }

    @Bean
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() {
        return new LifecycleBeanPostProcessor();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

其中securityManager类关闭了shiro自带的session,改用jwt验证,上面配置哪些接口需要鉴权,哪些不需要鉴权,鉴权接口走JWTFilter类拦截

JWTFilter拦截类(在shiro配置类千万不要用@Bean的方式去实例化,否则所有接口都会去鉴权)

package com.pwl.shiro.filter;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.pwl.shiro.common.ResultVO;
import com.pwl.shiro.exception.ShiroException;
import com.pwl.shiro.ream.JWTToken;
import com.pwl.shiro.util.JWTUtil;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.concurrent.TimeUnit;

/**
 * @author Pan Weilong
 * @date 2019/6/24 22:24
 * @description: 接口.
 */
public class JWTFilter extends BasicHttpAuthenticationFilter {

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    private String secret;

    private Integer tokenExpireTime;

    private StringRedisTemplate stringRedisTemplate;

    public JWTFilter(String secret, Integer tokenExpireTime, StringRedisTemplate stringRedisTemplate) {
        this.secret = secret;
        this.tokenExpireTime = tokenExpireTime;
        this.stringRedisTemplate = stringRedisTemplate;
    }

    /**
     * 判断用户是否想要登入。
     * 检测header里面是否包含Authorization字段即可
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        HttpServletRequest req = (HttpServletRequest) request;
        String authorization = req.getHeader("Authorization");
        return authorization != null;
    }

    /**
     * 执行登录流程
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String authorization = httpServletRequest.getHeader("Authorization");

        JWTToken token = new JWTToken(authorization);
        // 提交给realm进行登入,如果错误他会抛出异常并被捕获
        getSubject(request, response).login(token);
        // 如果没有抛出异常则代表登入成功,返回true
        return true;
    }

    /**
     * @Author Pan Weilong
     * @Description 是否允许访问 true允许访问。当方法返回false时才会执行isAccessAllowed方法
     * @Date 15:23 2019/6/27
     * @Param [request, response, mappedValue]
     * @return boolean
     **/
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue){
        if (isLoginAttempt(request, response)) {
            //登录验证
            try {
                executeLogin(request, response);
            } catch (Exception e) {
                HttpServletRequest httpServletRequest = (HttpServletRequest) request;
                String token = httpServletRequest.getHeader("Authorization");
                //处理token过期的问题,登入成功后将token存入redis,并将过期时间设置为token过期的2倍,
                //当token过期后,判断redis中的token是否存在,存在就生成新的token给前端,否则token过期,重新登录
                //这里没有处理并发问题
                if(stringRedisTemplate.hasKey(token)){
                    //重新生成token
                    String newToken = JWTUtil.sign(JWTUtil.getUserId(token), secret);
                    stringRedisTemplate.opsForValue().set(newToken,newToken);
                    stringRedisTemplate.expire(newToken,2*tokenExpireTime, TimeUnit.MINUTES);
                    //删掉以前的token
                    stringRedisTemplate.delete(token);
                    HttpServletResponse httpServletResponse = (HttpServletResponse) response;
                    httpServletResponse.setHeader("Authorization", newToken);
                    httpServletResponse.setHeader("Access-Control-Expose-Headers", "Authorization");
                    return true;
                }
                throw new AuthenticationException("token有误");
            }
        }else{
            throw new ShiroException("token不能为空");
        }
        //url鉴权
        Subject subject = getSubject(request,response);
        //获取请求方式
        String method = WebUtils.toHttp(request).getMethod();
        //subject.isPermitted返回false表示未授权 true已授权 会去调用ream中的授权 这里控制的方法级别 /GET/sysUser/**
        //如果访问的url没有被授权则会拒绝访问,走访问拒绝的处理逻辑onAccessDenied,有则放行
        return subject.isPermitted("/"+method+getPathWithinApplication(request));
    }

    /**
     * onAccessDenied:当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied

     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {
        logger.debug("当 isAccessAllowed 返回 false 的时候,才会执行 method onAccessDenied ");
        HttpServletRequest request =(HttpServletRequest) servletRequest;
        HttpServletResponse response =(HttpServletResponse) servletResponse;
        logger.debug("授权失败,禁止访问:{}",getPathWithinApplication(request));
        ObjectMapper objectMapper = new ObjectMapper();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json; charset=utf-8");
        ResultVO result = new ResultVO<>();
        result.setCode(403);
        result.setMsg("授权失败,禁止访问");
        PrintWriter printWriter = response.getWriter();
        printWriter.append(objectMapper.writeValueAsString(result));
        return false;
    }

    /**
     * 对跨域提供支持
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        HttpServletResponse httpServletResponse = (HttpServletResponse) response;
        httpServletResponse.setHeader("Access-control-Allow-Origin", httpServletRequest.getHeader("Origin"));
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        httpServletResponse.setHeader("Access-Control-Allow-Headers", httpServletRequest.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }
}

被拦截的接口会进入isAccessAllowed方法,这个方法回去判断token的合法性以及接口的鉴权,在这个方法里抛出的异常无法被我们自定义全局异常拦截到,所以添加处理filter抛出的异常,相当于springboot全局异常类

package com.pwl.shiro.controller;

import org.springframework.boot.autoconfigure.web.ErrorProperties;
import org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController;
import org.springframework.boot.autoconfigure.web.servlet.error.ErrorViewResolver;
import org.springframework.boot.web.servlet.error.DefaultErrorAttributes;
import org.springframework.boot.web.servlet.error.ErrorAttributes;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestControllerAdvice;

import javax.servlet.http.HttpServletRequest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Pan Weilong
 * @date 2019/6/25 21:55
 * @description: 处理filter抛出的异常,相当于springboot全局异常
 */
@RestControllerAdvice
public class ExceptionController extends BasicErrorController {

    public ExceptionController() {
        super(new DefaultErrorAttributes(), new ErrorProperties());
    }
    public ExceptionController(ErrorAttributes errorAttributes, ErrorProperties errorProperties) {
        super(errorAttributes, errorProperties);
    }

    public ExceptionController(ErrorAttributes errorAttributes, ErrorProperties errorProperties, List errorViewResolvers) {
        super(errorAttributes, errorProperties, errorViewResolvers);
    }

    /**
     * @Author Pan Weilong
     * @Description 处理传过来的异常(解决自定义全局异常捕获不到的问题)
     * @Date 17:16 2019/6/27
     * @Param [request]
     * @return org.springframework.http.ResponseEntity>
     **/
    @Override
    @RequestMapping(produces = {MediaType.APPLICATION_JSON_VALUE})
    public ResponseEntity> error(HttpServletRequest request) {
        Map body = getErrorAttributes(request, isIncludeStackTrace(request, MediaType.ALL));
        HttpStatus status = getStatus(request);
        String code=body.get("status").toString();
        String message = body.get("message").toString();
        Map map = new HashMap();
        map.put("code",code);
        map.put("msg",message);
        return new ResponseEntity>(map, status);
    }
}

认证类realm

package com.pwl.shiro.ream;

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.pwl.shiro.entity.SysUser;
import com.pwl.shiro.service.SysPermissionService;
import com.pwl.shiro.service.SysUserService;
import com.pwl.shiro.util.JWTUtil;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;

import java.util.ArrayList;
import java.util.List;

/**
 * @author Pan Weilong
 * @date 2019/6/20 20:11
 * @description: 认证和鉴权接口
 */
public class UserRealm extends AuthorizingRealm {

    private static final Logger LOGGER = LoggerFactory.getLogger(UserRealm.class);

    //token密钥
    @Value("${security.secret}")
    private String secret;

    @Autowired
    private SysUserService sysUserService;

    @Autowired
    private SysPermissionService sysPermissionService;

    /**
     * 大坑!,必须重写此方法,不然Shiro会报错
     */
    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof JWTToken;
    }

    /**
     * 授权
     *
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //String userId=principals.getPrimaryPrincipal().toString();
        //从数据库读取资源
        //可以控制到GET POST请求
        List sysPermissions=new ArrayList<>();
        sysPermissions.add("/GET/sysUser/**");
        sysPermissions.add("/DELETE/sysUser");
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(sysPermissions);
        return info;
    }

    /**
     * 认证流程
     *
     * @param
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken auth) throws AuthenticationException {
        String token = (String) auth.getCredentials();
        // 解密获得userId,用于和数据库进行对比
        String userId = JWTUtil.getUserId(token);
        if (userId == null) {
            throw new AuthenticationException("token invalid");
        }
        //查询用户是否存在
        Wrapper objectEntityWrapper = new EntityWrapper<>();
        objectEntityWrapper.eq("user_id","1");
        SysUser sysUser = sysUserService.selectOne(objectEntityWrapper);
        if (sysUser == null) {
            throw new AuthenticationException("User didn't existed!");
        }
        //校验token的正确性
        if (! JWTUtil.verify(token, userId, secret)) {
            throw new AuthenticationException("Token expired or incorrect.");
        }
        return new SimpleAuthenticationInfo(token, token, "my_realm");
    }

}

上面是执行登录验证和URL权限验证(我们只需要查询用户可以访问的全部接口并放入SimpleAuthorizationInfo中,关于匹配交给shiro来做,重写implies方法)

关于URL匹配的问题,shiro已经帮我们做了,继承PermissionResolver

package com.pwl.shiro.ream;

import org.apache.shiro.authz.Permission;
import org.apache.shiro.authz.permission.PermissionResolver;

/**
 * @author Pan Weilong
 * @date 2019/6/25 17:38
 * @description: 接口.
 */
public class UrlPermissionResolver implements PermissionResolver {
    @Override
    public Permission resolvePermission(String s) {
        return new UrlPermission(s);
    }
}
package com.pwl.shiro.ream;

import org.apache.shiro.authz.Permission;
import org.apache.shiro.util.AntPathMatcher;
import org.apache.shiro.util.PatternMatcher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Pan Weilong
 * @date 2019/6/25 17:42
 * @description: 接口.
 */
public class UrlPermission implements Permission {

    private static final Logger logger = LoggerFactory.getLogger(UrlPermission.class);

    //在 Realm 的授权方法中,由数据库查询出来的权限字符串
    private String url;

    public UrlPermission(String url){
        this.url = url;
    }

    /**
     * 一个很重要的方法,用户判断 Realm 中设置的权限和从数据库或者配置文件中传递进来的权限信息是否匹配
     * 如果 Realm 的授权方法中,一个认证主体有多个权限,会进行遍历,直到匹配成功为止
     * this.url 是在遍历状态中变化的
     *
     * urlPermission.url 是从 subject.isPermitted(url)
     * 传递到 UrlPermissionResolver 中传递过来的,就一个固定值
     *
     * @param permission
     * @return
     */
    @Override
    public boolean implies(Permission permission) {
        if(!(permission instanceof UrlPermission)){
            return false;
        }
        //
        UrlPermission urlPermission = (UrlPermission)permission;
        PatternMatcher patternMatcher = new AntPathMatcher();

        logger.debug("this.url(来自数据库中存放的通配符数据),在 Realm 的授权方法中注入的 => " + this.url);
        logger.debug("urlPermission.url(来自浏览器正在访问的链接) => " +  urlPermission.url);
        boolean matches = patternMatcher.matches(this.url,urlPermission.url);
        logger.debug("matches => " + matches);
        return matches;
    }
}

源码地址:https://github.com/James-Pan0525/springboot-shiro-jwt.git

 

你可能感兴趣的:(shiro)