8

秒极啊!手把手带你进行shiro授权拦截器的重写,学到了学到了

 3 years ago
source link: https://segmentfault.com/a/1190000038368043
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

shiro整合前后端分离的springboots,Vue项目真的是有很多大坑啊。

今天我的主题是:如何设置shiro过滤器。

遇到问题:我的项目是前后端分离的,shiro里面有一个shiroFilterFactoryBean.setUnauthorizedUrl(“你自己的url”);

函数

fIbMZnn.png!mobile

这是什么意思呢:这表示如果你访问了一个需要权限的url,但是目前你登陆的角色没有权限,那么页面默认跳转的地址。

看着似乎是没啥毛病。

但是!!!

如果是前后端分离怎么办呢?后端shiro让项目跳向前端某个页面,这里是前后端分离式的啊!当前端的用户(没权限)发送了一个请求被shiro直接拦截了,前端一脸懵不知道发生什么问题了,所以傻傻的在控制台返回一段红色的跨域错误信息。

那么我们应该怎么解决呢?

你肯定会想那就让后端让前端跳转啊,这里我要说,既然是前后端分离的话,后端只能给前端发送信息让前端根据返回的信息自行处理。

但是问题又来了,shiro这玩意直接把前端请求拦截了啥都不返回,啥都不说一声,搞的前端还误解是跨域问题。

那么我们就应该去改写shiro的拦截器,让它处理方式更人性化。

不废话了,开始主题

开始改写拦截器:

qY3UzyF.png!mobile

//package com.igeekhome.ccs.tool.config;  //写自己的当前目录

import lombok.SneakyThrows;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class MyPermsFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        System.out.println("isAccessDenied");
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        System.out.println("subject:"+subject.getPrincipal());
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }

        for(int i=0;i<rolesArray.length;i++){
            if(subject.isPermitted(rolesArray[i])){
                System.out.println("rolealist:"+rolesArray[i]);
                return true;
            }
        }
        return false;
    }

    @SneakyThrows
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        System.out.println("onAccessDenied");
        Subject subject = getSubject(request, response);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        // If the subject isn't identified, redirect to login URL
        if (subject.getPrincipal() == null) {
//            saveRequestAndRedirectToLogin(request, response);     //没登陆就进入重新登陆接口,这里前后端分离不需要
            System.out.println("没登陆");
            String objectStr= "{'name':'没登陆'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
        } else {
            System.out.println("没权限");
            String objectStr= "{'name':'没权限'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
//            JSONObject json = new JSONObject();
//            json.put("state","403");
//            json.put("msg","登录已失效,请重新登录!");
//            out.println(json);
//            out.flush();
//            out.close();
        }

        return false;
    }
}

首先看到

protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)

也就是第一个函数

解释一下:这个函数每次shiro启动都会运行,看到参数ServletRequest request, ServletResponse response这两个参数是为当前用户身份确认起作用的,再看到Object mappedValue,它里面存在你之前shiro配置文件里存入的权限要求:

V3Q7vii.png!mobile

我这里存了一个:

filterChainDefinitionMap.put("/detail/getclass","perms[user:teacher]");

所以mappedValue里存的应该是user:teacher,当然前提是把它转化为String

vaMNBnY.png!mobile

再看到代码:

jeA7Rbj.png!mobile

这是看看你之前有没有存权限,如果没存的话(列表大小为0或列表为空)返回true,shiro就不拦你了。

再往下看:

6VFFbu6.png!mobile

这里subject是什么?你往上看找到:

NfAFfeA.png!mobile

这个就是获取你当前登陆的用户。

然后注意了!

if(subject.isPermitted(rolesArray[i])){
                System.out.println("rolealist:"+rolesArray[i]);
                return true;
            }

这是干嘛,这就是拿当前用户判断isPermitted(),当前用户有没有perms的内容,也就是判断subject里面有没有”user:teacher”权限,然后这里有个大坑,网上基本上所有教程代码是这样的:

eAjINzu.png!mobile

他们是hasRole,其实也没毛病,但是我之前设置的是

6nERry.png!mobile

perms所以要用isPermitted,你要发清楚你加的是什么权限。

接着下一步:

如果找到了返回true,没找到从for循环出来,直接返回false。

false出现了,怎么办呢?这时候第二个函数

protected boolean onAccessDenied(ServletRequest request, ServletResponse response)

知道你返回是false的话它就会运行,看红色框内

rARNJbq.png!mobile

这代表什么意思,当然是获取当前用户的username了也就是标识码了,这里不懂得说明shiro你还是没弄懂()因为之前的自定义Realm类用过了。

如果subject.getPrincipal() == null说明没有用户名,意味着你还没有登陆呢,以前代码这里是一个跳转函数(让前端跳到登陆页面,但是我是前后端分离所以没用!),这里我改成了

String objectStr= "{'name':'没登陆'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);

这是把自定义的objectStr 字符串转化为jsob格式,然后用PrintWriter返回给前端,这样一旦出错那么shiro除了拦截请求外还会给前端发一个 "{‘name’:‘没登陆’}“的json信息,这些前端就不懵了就不会说什么跨域问题了。而是得到了json数据。然后下面的else部分内容就不说了,和上面几乎一样给前端发送一个”{‘name’:‘没权限’}"的信息,这意味着你权限出错了,并且用户也登陆了,那么就只有一个可能导致第一个函数返回false,那就是你本来就没有权限!

好了好了说了一大堆。

[]( https://blog.csdn.net/lanlans...

老规矩,给那些赶时间的朋友们:

快餐:

总结:

三个点:

1.为什么要写拦截器,并且如何去重写拦截器,上面说了哦。

2.拦截器文件第一个函数里是用subject.isPermitted(rolesArray[i])还是subject.hasRole(rolesArray[i])根据自己在之前设置信息判断。

YvM7veE.png!mobile

26z6ny7.png!mobile

我用的perms所以用subject.isPermitted(rolesArray[i])

3.就是这么向前端返回数据信息

IFNFrmF.png!mobile

然后贴出四分代码:

shiroConfig:

package //自己的包;

import org.apache.shiro.authc.credential.HashedCredentialsMatcher;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

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

@Configuration
public class shiroConfig {
    @Bean(name = "shiroFilter")
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        shiroFilterFactoryBean.setLoginUrl("/index/login");
        shiroFilterFactoryBean.setSuccessUrl("/Station/noauth");
//        shiroFilterFactoryBean.setUnauthorizedUrl("/notRole");
         shiroFilterFactoryBean.setUnauthorizedUrl("/Station/noauth");
//        shiroFilter.setLoginUrl("");//身份认证失败,则跳转到登录页面的配置 没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
//        shiroFilter.setSuccessUrl("");//登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
//        shiroFilter.setUnauthorizedUrl("");//没有权限默认跳转的页面
//        shiroFilter.setFilterChainDefinitions("");//filterChainDefinitions的配置顺序为自上而下,以最上面的为准
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        // <!-- authc:所有url都必须认证通过才可以访问; anon:所有url都都可以匿名访问-->
        filterChainDefinitionMap.put("/Station/**", "anon");
        filterChainDefinitionMap.put("/index/**", "anon");
//        filterChainDefinitionMap.put("/detail/**", "anon");
//        filterChainDefinitionMap.put("/detail/**", "anon");
//        filterChainDefinitionMap.put("/Goods/**", "authc");
        //主要这行代码必须放在所有权限设置的最后,不然会导致所有 url 都被拦截 剩余的都需要认证
//        filterChainDefinitionMap.put("/**", "authc");
        filterChainDefinitionMap.put("/detail/getclass","perms[user:teacher]");

        // 1、创建过滤器Map,用来装自定义过滤器
        LinkedHashMap<String, Filter> map = new LinkedHashMap<>();

        // 2、将自定义过滤器放入map中,如果实现了自定义授权过滤器,那就必须在这里注册,否则Shiro不会使用自定义的授权过滤器
        map.put("perms", new MyPermsFilter());

        // 3、将过滤器Ma绑定到shiroFilterFactoryBean上
        shiroFilterFactoryBean.setFilters(map);

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
        return shiroFilterFactoryBean;
    }
    @Bean
    public SecurityManager securityManager(CustomRealm realm){
        DefaultWebSecurityManager defaultWebSecurityManager = new DefaultWebSecurityManager();
        defaultWebSecurityManager.setRealm(realm);
        return  defaultWebSecurityManager;
    }
    @Bean
    public CustomRealm customRealm() {
        CustomRealm customRealm = new CustomRealm();
        customRealm.setCredentialsMatcher(hashedCredentialsMatcher());
        return customRealm;
    }
    @Bean
    public HashedCredentialsMatcher hashedCredentialsMatcher(){
        HashedCredentialsMatcher hashedCredentialsMatcher = new HashedCredentialsMatcher();
        // 使用md5 算法进行加密
        hashedCredentialsMatcher.setHashAlgorithmName("md5");
        // 设置散列次数: 意为加密几次
        hashedCredentialsMatcher.setHashIterations(2);

        return hashedCredentialsMatcher;
    }
}

CustomRealm:

package //自己的包;

import com.igeekhome.ccs.biz.IndexBiz;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AccountException;
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.apache.shiro.util.ByteSource;
import org.apache.tomcat.websocket.AuthenticationException;
import org.springframework.beans.factory.annotation.Autowired;

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

public class CustomRealm extends AuthorizingRealm {
//    @Autowired
//    private LoginService loginService;
    @Autowired
    IndexBiz indexBiz;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = (String) SecurityUtils.getSubject().getPrincipal();
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        Set<String> stringSet = new HashSet<>();
        if (indexBiz.getsatte(username).equals("老师")){
            System.out.println("老师");
            stringSet.add("user:teacher");
        }else {
            System.out.println("学生");
            stringSet.add("user:student");
        }
        info.setStringPermissions(stringSet);
        return info;
    }

    /**
     * 这里可以注入userService,为了方便演示,我就写死了帐号了密码
     * private UserService userService;
     * <p>
     * 获取即将需要认证的信息
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) {
        System.out.println("-------身份认证方法--------");
        String userName = (String) authenticationToken.getPrincipal();
        String userPwd = new String((char[]) authenticationToken.getCredentials());
        //根据用户名从数据库获取密码
        String password = indexBiz.getpassword(userName);

        if (indexBiz.getsatte(userName)==null) {
            throw new AccountException("用户名错误");
        }
//        else if (!userPwd.equals(password)) {
//            throw new AccountException("密码不正确");
//        }
        String dbPwd = indexBiz.getpassword(userName);
        return new SimpleAuthenticationInfo(userName,dbPwd, ByteSource.Util.bytes(userName + "salt"),getName());
    }

}

MyPermsFilter:

package //自己的包;

import lombok.SneakyThrows;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authz.AuthorizationFilter;
import org.springframework.boot.configurationprocessor.json.JSONObject;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import java.io.IOException;
import java.io.PrintWriter;

public class MyPermsFilter extends AuthorizationFilter {

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue)
            throws Exception {
        System.out.println("isAccessDenied");
        Subject subject = getSubject(request, response);
        String[] rolesArray = (String[]) mappedValue;
        System.out.println("subject:"+subject.getPrincipal());
        if (rolesArray == null || rolesArray.length == 0) {
            //no roles specified, so nothing to check - allow access.
            return true;
        }

        for(int i=0;i<rolesArray.length;i++){
            if(subject.isPermitted(rolesArray[i])){//subject.hasRole(rolesArray[i])
                System.out.println("rolealist:"+rolesArray[i]);
                return true;
            }
        }
        return false;
    }

    @SneakyThrows
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws IOException {
        System.out.println("onAccessDenied");
        Subject subject = getSubject(request, response);
        response.setCharacterEncoding("UTF-8");
        response.setContentType("text/html;charset=UTF-8");
        // If the subject isn't identified, redirect to login URL
        if (subject.getPrincipal() == null) {
//            saveRequestAndRedirectToLogin(request, response);     //没登陆就进入重新登陆接口,这里前后端分离不需要
            System.out.println("没登陆");
            String objectStr= "{'name':'没登陆'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
        } else {
            System.out.println("没权限");
            String objectStr= "{'name':'没权限'}";
            JSONObject jsonObject=new JSONObject(objectStr);
            PrintWriter wirte = null;
            wirte = response.getWriter();
            wirte.print(jsonObject);
//            JSONObject json = new JSONObject();
//            json.put("state","403");
//            json.put("msg","登录已失效,请重新登录!");
//            out.println(json);
//            out.flush();
//            out.close();
        }

        return false;
    }

}

最后

文章的最后给大家安利一个福利,关注公众号:前程有光,领取一线大厂Java面试题总结+各知识点学习思维导+一份300页pdf文档的Java核心知识点总结!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK