使用shiro/spring security拦截器Filter,整合jwt,token进行校验【shiro/spring security 拦截token】

导读:本篇文章讲解 使用shiro/spring security拦截器Filter,整合jwt,token进行校验【shiro/spring security 拦截token】,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

shiro

默认的拦截器
下面的代码为请求声明其拦截器的类型

filterChainDefinitionMap.put("/login/index","anon");
filterChainDefinitionMap.put("/**","authc");

整合了shiro安全框架后,当运行一个Web应用程序时,Shiro将会创建一些有用的默认 Filter 实例,并自动地将它们置为可用,而这些默认的 Filter 实例是被 DefaultFilter 枚举类定义的,当然我们也可以自定义 Filter 实例

常用的主要就是 anon,authc,user,roles,perms 等

注意:anon, authc, authcBasic, user 是第一组认证过滤器,perms, port, rest, roles, ssl 是第二组授权过滤器,要通过授权过滤器,就先要完成登陆认证操作(即先要完成认证才能前去寻找授权) 才能走第二组授权器(例如访问需要 roles 权限的 url,如果还没有登陆的话,会直接跳转到 shiroFilterFactoryBean.setLoginUrl(); 设置的 url

自定义JwtFilter
下面是采用自定义的拦截器去完成token校验,首先定义一个JwtFilter类继承提供的父拦截器,并重写其中的方法

package com.ojj.config;

import com.ojj.config.vo.JwtToken;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.apache.shiro.web.util.WebUtils;
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;

/**
 * @author ojj
 * @title: JwtFilter
 * @projectName test
 * @description: 拦截器
 * @date 2022/1/6 14:57
 */
public class JwtFilter extends BasicHttpAuthenticationFilter {

    /**
     * 拦截器的前置  最先执行的 这里只做了一个跨域设置
     */
    @Override
    protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
        System.out.println("JwtFilter -----> preHandle() 方法执行");
        HttpServletRequest req = (HttpServletRequest) request;
        HttpServletResponse res = (HttpServletResponse) response;
        res.setHeader("Access-control-Allow-Origin", req.getHeader("Origin"));
        res.setHeader("Access-control-Allow-Methods", "GET,POST,OPTIONS,PUT,DELETE");
        res.setHeader("Access-control-Allow-Headers", req.getHeader("Access-Control-Request-Headers"));
        // 跨域时会首先发送一个option请求,这里我们给option请求直接返回正常状态
        if (req.getMethod().equals(RequestMethod.OPTIONS.name())) {
            res.setStatus(HttpStatus.OK.value());
            return false;
        }
        return super.preHandle(request, response);
    }

    /**
     * preHandle 执行完之后会执行这个方法
     * 再这个方法中 我们根据条件判断去去执行isLoginAttempt和executeLogin方法
     */

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        System.out.println("JwtFilter -----> isAccessAllowed() 方法执行");
        /**
         * 先去调用 isLoginAttempt方法 字面意思就是是否尝试登陆 如果为true
         * 执行executeLogin方法
         */
        if (isLoginAttempt(request, response)) {
            executeLogin(request, response);
            return true;
        }

        return false;
    }


    /**
     * 这里我们只是简单去做一个判断请求头中的token信息是否为空
     * 如果没有我们想要的请求头信息则直接返回false
     */
    @Override
    protected boolean isLoginAttempt(ServletRequest request, ServletResponse response) {
        System.out.println("JwtFilter -----> isLoginAttempt() 方法执行");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("X-Access-Token:");
        return token != null;
    }


    /**
     * 执行登陆
     * 因为已经判断token不为空了,所以直接执行登陆逻辑
     * 讲token放入JwtToken类中去
     * 然后getSubject方法是调用到了MyRealm的 执行方法  因为上面我是抛错的所有最后做个异常捕获就好了
     */
    @Override
    protected boolean executeLogin(ServletRequest request, ServletResponse response) {
        System.out.println("JwtFilter -----> executeLogin() 方法执行");
        HttpServletRequest req = (HttpServletRequest) request;
        String token = req.getHeader("X-Access-Token:");
        JwtToken jwtToken = new JwtToken(token);
        //然后交给自定义的realm对象去登陆, 如果错误他会抛出异常并且捕获

        System.out.println("-----执行登陆开始-----");
        getSubject(request, response).login(jwtToken);
        System.out.println("-----执行登陆结束----- 未抛出异常");
        return true;
    }


}

改造shiroConfig

import javax.servlet.Filter;

   		@Bean()
    public ShiroFilterFactoryBean getShiroFilterFactoryBean(@Qualifier("securityManager") DefaultWebSecurityManager defaultWebSecurityManager){

        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(defaultWebSecurityManager);

        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();

        Map<String, Filter> filterMap = new HashMap<>();

        filterChainDefinitionMap.put("/login/loginInfo","anon");
        filterChainDefinitionMap.put("/login/index","anon");

        //引入自定义拦截器,命名为jwt
        filterMap.put("jwt",new JwtFilter());
        //放到shiro工厂中
        shiroFilterFactoryBean.setFilters(filterMap);
        //其余接口一律拦截
        //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截
        filterChainDefinitionMap.put("/**","jwt");//替换默认的拦截器为自定义的jwt
//        filterChainDefinitionMap.put("/**","authc");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        shiroFilterFactoryBean.setLoginUrl("/login/tologin");
        return shiroFilterFactoryBean;
    }

此时本以为运行会成功,居然出错了,报错的关键的内容如下:

Realm does not support authentication token

大概是因为每一个Ream都有一个supports方法,用于检测是否支持此Token, 而在该函数中,默认的采用了return false;
所以在自己的Realm中重写方法,JwtToken是用来传递校验信息的,这里用了自定义类型。

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

而之前是用

UsernamePasswordToken token = new UsernamePasswordToken(username, password);
subject.login(token);

现在访问需要权限的接口,就会被JwtFilter拦截,然后判断是否有token,再然后可以加入token校验,如果没有token或者不符合权限就会被拦截。

这里由于我写的页面没带token,不太懂把token放到请求头信息中的X-Access-Token:。所以意会一下这里

所以登录成功后点击add/update 还是不会成功,由于上面的原因。

spring security

上面是shiro 对于spring security类似 直接看过滤器代码

package com.xii.security.filter;

import com.xii.commonutils.R;
import com.xii.commonutils.ResponseUtil;
import com.xii.security.security.TokenManager;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.util.StringUtils;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @description: 访问过滤器
 *
 * @return {@link null}
 * @author  wangxihao
 * @email wangxh0108@163.com
**/
public class TokenAuthenticationFilter extends BasicAuthenticationFilter {
    private TokenManager tokenManager;
    private RedisTemplate redisTemplate;

    public TokenAuthenticationFilter(AuthenticationManager authManager, TokenManager tokenManager,RedisTemplate redisTemplate) {
        super(authManager);
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        logger.info("================="+req.getRequestURI());
        if(req.getRequestURI().indexOf("admin") == -1) {
            chain.doFilter(req, res);
            return;
        }

        UsernamePasswordAuthenticationToken authentication = null;
        try {
            authentication = getAuthentication(req);
        } catch (Exception e) {
            ResponseUtil.out(res, R.error());
        }

        if (authentication != null) {
            SecurityContextHolder.getContext().setAuthentication(authentication);
        } else {
            ResponseUtil.out(res, R.error());
        }
        chain.doFilter(req, res);
    }

    private UsernamePasswordAuthenticationToken getAuthentication(HttpServletRequest request) {
        // token置于header里
        String token = request.getHeader("token");
        if (token != null && !"".equals(token.trim())) {
            String userName = tokenManager.getUserFromToken(token);

            List<String> permissionValueList = (List<String>) redisTemplate.opsForValue().get(userName);
            Collection<GrantedAuthority> authorities = new ArrayList<>();
            for(String permissionValue : permissionValueList) {
                if(StringUtils.isEmpty(permissionValue)) continue;
                SimpleGrantedAuthority authority = new SimpleGrantedAuthority(permissionValue);
                authorities.add(authority);
            }

            if (!StringUtils.isEmpty(userName)) {
                return new UsernamePasswordAuthenticationToken(userName, token, authorities);
            }
            return null;
        }
        return null;
    }
}

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/75460.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!