JWT+Shiro认证

JWT

JWT调试工具https://jwt.io/#debugger

JWT+Shiro认证_第1张图片

全称:Json Web Token

什么是JWT,是为了在网络应用环境间传递声明而执行的一种基于JSON的开放标准(RFC 7519),该token被设计为紧凑且安全的 ,特别适用于分布式站点单点登录(SSO)常见

JWT一般作为用户信息在客户端和服务端之间传递,以便于从资源服务器获取资源,也可以增加一些额外的其他业务逻辑所必须的声明信息,该token也可以直接用于认证,也可以被加密

JWT+Shiro进行登录认证

核心类一:ShiroConfig

方法一:配置SecurityManager,这个类是Shiro的核心类,用于管理所有用户

@Bean
    public DefaultWebSecurityManager securityManager(AccountRealm accountRealm,
                                                     SessionManager sessionManager,
                                                     RedisCacheManager redisCacheManager) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager(accountRealm);
        securityManager.setSessionManager(sessionManager);
        securityManager.setCacheManager(redisCacheManager);
        /*
         * 关闭shiro自带的session,详情见文档
         */
        DefaultSubjectDAO subjectDAO = new DefaultSubjectDAO();
        DefaultSessionStorageEvaluator defaultSessionStorageEvaluator
                = new DefaultSessionStorageEvaluator();
        defaultSessionStorageEvaluator.setSessionStorageEnabled(false);
        subjectDAO.setSessionStorageEvaluator(defaultSessionStorageEvaluator);
        securityManager.setSubjectDAO(subjectDAO);
        return securityManager;
    }

方法二:配置过滤器链

 @Bean
    public ShiroFilterChainDefinition shiroFilterChainDefinition() {
        DefaultShiroFilterChainDefinition chainDefinition = new DefaultShiroFilterChainDefinition();
        //将所有请求交给名为"jwt"的过滤器链进行处理
        //在下一个方法我们会看到“jwt"过滤器链由自定义的JwtFilter的实例jwtFilter实现
        chainDefinition.addPathDefinition("/**", "jwt");// 主要通过注解方式校验权限,这里都用jwtfilter进行拦截
        return chainDefinition;
    }

方法三:配置过滤器工厂,为过滤器工厂配置过滤器链以及过滤器

@Bean("shiroFilterFactoryBean")
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
                                                     ShiroFilterChainDefinition shiroFilterChainDefinition) {
    ShiroFilterFactoryBean shiroFilterFactory = new ShiroFilterFactoryBean();
    shiroFilterFactory.setSecurityManager(securityManager);
    Map filters = new HashMap<>();
    filters.put("jwt", jwtFilter);
    //设置filter为自定义的jwtFilter
    shiroFilterFactory.setFilters(filters);
    Map filterMap = shiroFilterChainDefinition.getFilterChainMap();
    shiroFilterFactory.setFilterChainDefinitionMap(filterMap);
    return shiroFilterFactory;
}

下面是这个方法的一些解释

JWT+Shiro认证_第2张图片

​ 意思是什么呢,shiro的过滤器链拦截所有的请求(”/**“),交给名为”jwt“的拦截器去处理,然后ShiroFilterFactory根据这个”jwt“字符串去找到自定义的过滤器对象jwtFilter去处理

JWT+Shiro认证_第3张图片

在登录接口之前会有一个全局的处理逻辑,来判断这个请求头中是否含有Jwt

如何用shiro实现这个全局处理逻辑,shiro采用了一个过滤器链

 @Bean("shiroFilterFactoryBean")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager,
                                                         ShiroFilterChainDefinition shiroFilterChainDefinition) {
        ShiroFilterFactoryBean shiroFilter = new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        Map filters = new HashMap<>();
        //这个就是自定义的过滤器,jwt
        filters.put("jwt", jwtFilter);
        //设置filter为自定义的jwtFilter
        shiroFilter.setFilters(filters);
        Map filterMap = shiroFilterChainDefinition.getFilterChainMap();
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    }

方法四:配置Redis作为Shiro的Session会话管理器

@Bean
public SessionManager sessionManager(RedisSessionDAO redisSessionDAO) {
    DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();

    // inject redisSessionDAO
    sessionManager.setSessionDAO(redisSessionDAO);
    return sessionManager;
}

核心类二:JwtFilter

​ 核心类SecurityManager中配置的自定义过滤器就是JwtFilter的实例,所以我们需要了解这个类需要定义一些什么方法,这些方法各自的作用是什么?

​ 首先是类的声明

public class JwtFilter extends AuthenticatingFilter

​ 我们可以看到JwtFilter这个类继承自AuthenticatiionFilter(认证过滤器)

​ 在自定义的过滤器类中,我们需要重写以下方法

  1. boolean preHandle(...)
  2. AuthenticationToken createToken(...)
  3. boolean isAccessAllowed(...)
  4. boolean onAccessDenied(...)
  5. boolean onLoginFailure()

第一个方法,对所有请求进行预处理

主要做几件事情

  1. 为响应头添加跨域相关的键值对(提供跨域支持):

    1. Access-Control-Allow-Origin
    2. Access-Control-Allow-Methods
    3. Access-Control-Allow-Headers
  2. 设置响应行的HTTP状态码为200 状态为OK
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        HttpServletRequest httpServletRequest = WebUtils.toHttp(request);
        HttpServletResponse httpServletResponse = WebUtils.toHttp(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"));
        // 跨域时会首先发送一个OPTIONS请求,这里我们给OPTIONS请求直接返回正常状态
        if (httpServletRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
            httpServletResponse.setStatus(org.springframework.http.HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

第二个方法:CreateToken

做的事情

  1. 获取请求头中的Authorization的信息,并转换为shiro能够识别的AuthenticationToken的实例
  2. 如果Authorization的值为空字符串,返回null,执行isAccessAllow()方法
@Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        /**
         * 获取 token token存储在请求头的 key为Authorization对应的value中
         * token是客户端第一次访问服务器,服务器根据用户的相关信息生成的一个令牌,此后用户再次访问服务器不用重新登录,只需要携带token访问即可
         */
        //1.获取HttpServletRequest
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        //2.根据浏览器添加在请求头中的Authorization的值,创建Shiro能够识别的Token对象AuthenticationToken
        String jwt = request.getHeader("Authorization");

        //空的话表示登录的时候没有携带jwt,访问异常,return null,会进入isAccessAllowed()的异常处理逻辑。
        if (StringUtils.isEmpty(jwt)) {
            return null;
        }
        //JwtToken是该方法返回值类型AuthenticationToken的自定义实现类
        return new JwtToken(jwt);
    }

方法二:

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        if (this.isLoginRequest(request, response)) {
            return true;
        }
        boolean allowed = false;
        try {
            allowed = executeLogin(request, response);
        } catch (IllegalStateException e) { //not found any token
            log.error("Not found any token");
        } catch (Exception e) {
            log.error("Error occurs when login", e);
        }
        return allowed || super.isPermissive(mappedValue);
    }

尝试进行登录,登录失败抛出异常

方法三:如果原先用户没有登录

  • 尝试获取token,并且判断token是否过期,过期抛出异常,否则执行登录
 @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String token = request.getHeader("Authorization");
        //如果token不为空,返回true,true表示可以继续计型web的相关操作
        if (StringUtils.isEmpty(token)) {
            return true;
        } else {
            // 判断是否已过期 校验jwt
            // 获取token的声明
            Claims claim = jwtUtils.getClaimByToken(token);
            //如果声明为空 或者token过期,抛出异常
            // 从claim中获取token过期时间,并采用isTokenExpired与现在的时间进行比对,
            if (claim == null || jwtUtils.isTokenExpired(claim.getExpiration())) {
                throw new ExpiredCredentialsException("token已失效,请重新登录!");
            }
        }
        // 执行自动登录
        return executeLogin(servletRequest, servletResponse);
    }

方法四:登录失败,抛出异常

    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) {

        HttpServletResponse httpServletResponse = (HttpServletResponse) response;

        Throwable throwable = e.getCause() == null ? e : e.getCause();
        R result = R.fail(throwable.getMessage());
        String json = JSONUtil.toJsonStr(result);

        try {
            httpServletResponse.getWriter().print(json);
        } catch (IOException ioException) {

        }
        return false;
    }

你可能感兴趣的:(shirojwt)