带你深入使用shiro,自定义token过滤器

Posted 可——叹——落叶飘零

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了带你深入使用shiro,自定义token过滤器相关的知识,希望对你有一定的参考价值。

文章目录

依赖

shiro自定义请求头token,实现权限过滤
废话不多说,直接上代码,如果不了解shiro的基本使用,建议先入门shiro

<dependency>
	<groupId>org.apache.shiro</groupId>
	<artifactId>shiro-spring</artifactId>
	<version>1.5.3</version>
</dependency>

大概结构

ShiroAuthFilter 自定义过滤器

自定义过滤器,extends AuthenticatingFilter 重写过滤token方法

package com.spshiro.auth;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.springframework.util.StringUtils;
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 落叶飘零
 * @version 1.0
 * @description: 会在ShiroBeanConfig的配置类里装载该 token过滤器
 * @date 2022/5/19 1:39 下午
 */
public class ShiroAuthFilter extends AuthenticatingFilter 
    @Override
    protected AuthenticationToken createToken(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception 
        String token = getToken((HttpServletRequest) servletRequest);
        if(StringUtils.hasText(token))
            //ShiroAuthToken是自己定义实现AuthenticationToken接口的token
           return new ShiroAuthToken(token);
        
        //executeLogin调用该方法token==null就抛异常,我们在onAccessDenied入口
        //重写方法没有token就提前返回并携带原因
        return null;
    

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) 
        return false;
    

    /**
     * 登录失败,重写方法返回失败原因
     * @param token
     * @param e
     * @param request
     * @param response
     * @return false
     */
    @Override
    protected boolean onLoginFailure(AuthenticationToken token, AuthenticationException e, ServletRequest request, ServletResponse response) 
        //重写登录失败,把登录失败的原因返回给前端
        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setContentType("application/json;charset=utf-8");
        httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
        httpResponse.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest)request).getHeader("Origin"));
        try 
            //处理登录失败的异常
            Throwable throwable = e.getCause() == null ? e : e.getCause();
            //返回401 new JsonResult.error(throwable.getMessage())
            httpResponse.getWriter().print("401");
         catch (IOException e1) 
            e1.printStackTrace();
        
        return false;
    

    /**
     * 程序入口 检验token是否携带,没携带就返回提示没有token
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception 
        //获取用户token
        String token = getToken((HttpServletRequest) servletRequest);
        //如果token不存在提前返回401
        if(!StringUtils.hasText(token))
            HttpServletResponse httpResponse = (HttpServletResponse) servletResponse;
            httpResponse.setHeader("Access-Control-Allow-Credentials", "true");
            httpResponse.setHeader("Access-Control-Allow-Origin", ((HttpServletRequest)servletRequest).getHeader("Origin"));
            //new JsonResult.error(invalid token)
            httpResponse.getWriter().print("401");
            return false;
        
        //executeLogin方法会校验createToken是否返回了非null AuthenticationToken
        //上面我们提前校验提前返回信息给前端
        return executeLogin(servletRequest, servletResponse);
    

    /**
     * 方法获取用户的token
     * @param request
     * @return token
     */
    public String getToken(HttpServletRequest request)
        //获取请求头
        String token = request.getHeader("token");
        //没有请求头就从参数里拿
        if (!StringUtils.hasText(token))
            token=request.getParameter("token");
        
        return token;
    

ShiroAuthRealm

package com.spshiro.auth;

import com.spshiro.entity.SysUserEntity;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.springframework.stereotype.Component;

import java.util.HashSet;
import java.util.Set;

/**
 * @author 落叶飘零
 * @version 1.0
 * @description: TODO
 * @date 2022/5/19 1:29 下午
 */
@Component
public class ShiroAuthRealm extends AuthorizingRealm 
    /**
     * 判断是否支持token的类型 ****important****
     * 每一个Ream都有一个supports方法,用于检测是否支持此Token,默认的采用了return false
     * @param token
     * @return
     */
    @Override
    public boolean supports(AuthenticationToken token) 
        return token instanceof ShiroAuthToken;
    
    /**
     * 授权 集合会与 @RequiresPermissions()声明的权限方法匹配,通常不调用权限的方法不会执行
     * @param principalCollection
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) 
        SysUserEntity user = (SysUserEntity) principalCollection.getPrimaryPrincipal();
        System.out.println("授权方法检索权限:当前的用户="+user);
        //权限集合
        Set<String> permsSet=new HashSet<>();
        //根据用户的信息查权限存入set
        if("123".equals(user.getUserId()))
            permsSet.add("sys:need");
        
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permsSet);
        return info;
    
    /**
     * 认证,登录的时候会调用
     * 调用时机
     * Subject subject = SecurityUtils.getSubject();
     * subject.login(token);
     * 在自定义token过滤器的executeLogin方法也会调用到上面两行,所以也会执行到下面的doGetAuthenticationInfo,每次请求都会认证
     * authenticationToken能强转成字符串,因为用的是自定义的String类型的token
     * @param authenticationToken
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException 
        String token = (String) authenticationToken.getPrincipal();
        //通过token到redis缓存里获取到对应用户user
        SysUserEntity userEntity=new SysUserEntity();
        if(token.equals("123"))
            userEntity.setUsername("落叶飘零");
            userEntity.setUserId("123");
            userEntity.setPassword("123");
            System.out.println("认证token");
        else
            throw new IncorrectCredentialsException("token失效");
        
        //user为空 token失效
        //throw new IncorrectCredentialsException("token失效,请重新登录");
        //也可以增加ip校验
        //HttpServletRequest request = HttpContextUtils.getHttpServletRequest();
        //            String ip = IPUtils.getIpAddr(request);
        //            if (!StringUtils.equals(ip, user.getLoginIp())) 
        //                throw new IncorrectCredentialsException("非法使用token");
        //            
        //是否锁定
        
        //userEntity参数会让 SysUserEntity user = (SysUserEntity) principalCollection.getPrimaryPrincipal();强转
        //同时可以做到程序全局能直接获取当前用户的信息
        //Subject subject = SecurityUtils.getSubject();
        // Object principal = subject.getPrincipal();
        //(SysUserEntity) principal;
        SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(userEntity, token, getName());
        return info;
    

ShiroAuthToken 自定义token

自定义token

package com.spshiro.auth;

import org.apache.shiro.authc.AuthenticationToken;

/**
 * @author 落叶飘零
 * @version 1.0
 * @description: 自定义token,在自定义token过滤器里使用
 * @date 2022/5/19 1:47 下午
 */
public class ShiroAuthToken implements AuthenticationToken 

    private String token;
    public ShiroAuthToken(String token)
        this.token=token;
    
    @Override
    public String getPrincipal() 
        return token;
    

    @Override
    public Object getCredentials() 
        return token;
    


ShiroBeanConfig

工厂及SecurityManager

package com.spshiro.config;

import com.spshiro.auth.ShiroAuthFilter;
import com.spshiro.auth.ShiroAuthRealm;
import org.apache.shiro.mgt.SecurityManager;
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 javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * @author 落叶飘零
 * @version 1.0
 * @description: TODO
 * @date 2022/5/19 1:25 下午
 */
@Configuration
public class ShiroBeanConfig 

    @Bean("securityManager")
    public SecurityManager securityManager(ShiroAuthRealm shiroAuthRealm)
    //ShiroAuthRealm是自定义reaml
        DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
        manager.setRealm(shiroAuthRealm);
        return manager;
    
    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager)
        ShiroFilterFactoryBean shiroFilter=new ShiroFilterFactoryBean();
        shiroFilter.setSecurityManager(securityManager);
        //装载自定义的token过滤器
        Map<String, Filter> tokenFilterMap=new HashMap<>();
        tokenFilterMap.put("oauth2",new ShiroAuthFilter());
        shiroFilter.setFilters(tokenFilterMap);
        //普通路径过滤规则
        Map<String, String> filterMap = new LinkedHashMap<>();
        //登录
        filterMap.put("/sys/login", "anon");
        //验证码
        filterMap.put("/captcha.jpg", "anon");
        // 注册用户
        filterMap.put("/sys/user/reg", "anon");
        //swagger
        filterMap.put("/swagger-resources/**", "anon");
        filterMap.put("/swagger/**", "anon");
        filterMap.put("/v2/api-docs", "anon");
        filterMap.put("/v2/api-docs-ext", "anon");
        filterMap.put("/swagger-ui.html", "anon");
        filterMap.put("/webjars/**", "anon");
        filterMap.put("/doc.html", "anon");
        filterMap.put("/druid/**", "anon");
        // api接口放权
        filterMap.put("/api/**", "anon");
        // websocket 放权
        filterMap.put("/ws/**", "anon");
        //微信登录放权
        filterMap.put("/auth/login", "anon");
        filterMap.put("/auth/code", "anon");
        //微信绑定用户放权
        filterMap.put("/auth/bind", "anon");
        //统一认证登录放权
        filterMap.put("/auth/cas", "anon");
        //注册、分享、邀请链接放权
        filterMap.put("/register", "anon");
        filterMap.put("/share", "anon");
        filterMap.put("/invite", "anon");
        //图片预览放权(主要用在内容中的图片)
        filterMap.put("/file/image/**", "anon");
        // 静态资源
        filterMap.put("/static/**", "anon");
        // 接收第三方系统消息放权
        filterMap.put("/sys/message/push","anon");

        //自定义的token过滤器拦截其他任何请求
        filterMap.put("/**", "oauth2");
        shiroFilter.setFilterChainDefinitionMap(filterMap);
        return shiroFilter;
    
    @Bean("lifecycleBeanPostProcessor")
    public LifecycleBeanPostProcessor lifecycleBeanPostProcessor() 
        return new LifecycleBeanPostProcessor();
    

    @Bean
    public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() 
        DefaultAdvisorAutoProxyCreator proxyCreator = new DefaultAdvisorAutoProxyCreator();
        proxyCreator.setProxyTargetClass(true);
        return proxyCreator;
    

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) 
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    


UserUtil 用户获取工具方法

全局获取当前user信息

package com.spshiro.util;

import com.spshiro.entity.SysUserEntity;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;

/**
 * @author 落叶飘零
 * (SysUserEntity) principal在realm认证传入SysUserEntity类型数据可强转
 * @version 1.0
 * @description: TODO
 * @date 2022/5/19 7:47 下午
 */
public class UserUtil 

以上是关于带你深入使用shiro,自定义token过滤器的主要内容,如果未能解决你的问题,请参考以下文章

带你深入使用shiro,自定义token过滤器

带你深入使用shiro,自定义token过滤器

shiro权限控制配置

自定义 UsernamePasswordToken 报错 java.lang.ClassCastException: org.apache.shiro.authc.UsernamePasswordT

shiro自定义过滤器冲突弹簧过滤器

Spring Boot环境下自定义shiro过滤器会过滤所有的url的问题