• springboot下使用shiro自定义filter的个人经验


    在springboot中使用shiro,由于没有了xml配置文件,因此使用的方法与spring中有些区别。在踩了无数个坑后,在此将springboot下使用shiro的步骤总结如下。由于本人对shiro的了解不是很深入,在实现了工作需求后就没有继续研究了,因此可能存在遗漏的地方或有错误的地方,还请多包涵。

    目标:

    在springboot中使用shiro

    1.实现用户的登录验证

    2.对于一些指定的url使用自定义的filter验证方式(不再使用shiro的realm验证)

    步骤:

    1.在pom.xml中添加shiro的依赖

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

    2.创建ShiroRealm.java,继承AuthorizingRealm类,重写登录认证方法与授权方法

    import User;
    import UserService;
    import org.apache.shiro.authc.*;
    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.springframework.beans.factory.annotation.Autowired;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class ShiroRealm extends AuthorizingRealm {
        //自己编写的service,注入,执行数据库查询方法用
        @Autowired
        private UserService userService;
        //认证.登录
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            UsernamePasswordToken utoken=(UsernamePasswordToken) token;//获取用户输入的token
            String username = utoken.getUsername();
            //这个User对象为自定义的JavaBean,使用userService从数据库中得到User对象(包含用户名、密码、权限3个字段)
            User user = userService.findUserByUserName(username);
            return new SimpleAuthenticationInfo(user, user.getPassword(),this.getClass().getName());//放入shiro.调用CredentialsMatcher检验密码
        }
        //授权
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principal) {
            User user=(User) principal.fromRealm(this.getClass().getName()).iterator().next();//获取session中的用户
            List<String> permissions=new ArrayList<>();
            //使用自定义的user对象获得权限字段,string类型,装入集合
            permissions.add(user.getRole());
    
            SimpleAuthorizationInfo info=new SimpleAuthorizationInfo();
            info.addStringPermissions(permissions);//将权限放入shiro中.(我的代码中其实没有用到权限相关)
            return info;
        }
    }
    

    3.创建ShiroConfiguration.java,使用@Configuration注解,是shiro的配置类,类似xml

    import java.util.LinkedHashMap;
    import java.util.Map;
    
    import org.apache.shiro.mgt.SecurityManager;
    import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
    import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
    import org.springframework.beans.factory.annotation.Qualifier;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Configuration;
    
    import javax.servlet.Filter;
    
    @Configuration
    public class ShiroConfiguration {
    
        @Bean
        public ShiroFilterFactoryBean shirFilter(SecurityManager securityManager){
            ShiroFilterFactoryBean shiroFilterFactoryBean  = new ShiroFilterFactoryBean();
            // 必须设置 SecurityManager
            shiroFilterFactoryBean.setSecurityManager(securityManager);
    
            /*重要,设置自定义拦截器,当访问某些自定义url时,使用这个filter进行验证*/
            Map<String, Filter> filters = new LinkedHashMap<String, Filter>();
            //如果map里面key值为authc,表示所有名为authc的过滤条件使用这个自定义的filter
            //map里面key值为myFilter,表示所有名为myFilter的过滤条件使用这个自定义的filter,具体见下方
            filters.put("myFilter", new MyFilter());
            shiroFilterFactoryBean.setFilters(filters);
            /*---------------------------------------------------*/
    
            //拦截器
            Map<String,String> filterChainDefinitionMap = new LinkedHashMap<String,String>();
            //配置退出过滤器,其中的具体的退出代码Shiro已经替我们实现了
            filterChainDefinitionMap.put("/logout", "logout");
            //  anon:所有url都都可以匿名访问;
            //  authc: 需要认证才能进行访问;
            //  user:配置记住我或认证通过可以访问;
            //放开静态资源的过滤
            filterChainDefinitionMap.put("/css/**", "anon");
            filterChainDefinitionMap.put("/js/**", "anon");
            filterChainDefinitionMap.put("/img/**", "anon");
            //放开登录url的过滤
            filterChainDefinitionMap.put("/loginController", "anon");
            ///
            //对于指定的url,使用自定义filter进行验证
            filterChainDefinitionMap.put("/targetUrl", "myFilter");
            //可以配置多个filter,用逗号分隔,按顺序过滤,下方表示先通过自定义filter的验证,再通过shiro默认过滤器的验证
            //filterChainDefinitionMap.put("/targetUrl", "myFilter,authc");
            ///
            //过滤链定义,从上向下顺序执行,一般将 /**放在最为下边
            //url从上向下匹配,当条件匹配成功时,就会进入指定filter并return(不会判断后续的条件),因此这句需要在最下边
            filterChainDefinitionMap.put("/**", "authc");
    
            //如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
            shiroFilterFactoryBean.setLoginUrl("/login");
            // 登录成功后要跳转的链接
            shiroFilterFactoryBean.setSuccessUrl("/loginSuccess");
            // 未授权界面
            shiroFilterFactoryBean.setUnauthorizedUrl("/403");
    
            shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
    
            return shiroFilterFactoryBean;
        }
    
        @Bean
        public SecurityManager securityManager(){
            DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
            return securityManager;
        }
        //配置核心安全事务管理器
        @Bean(name="securityManager")
        public SecurityManager securityManager(@Qualifier("shiroRealm") ShiroRealm shiroRealm) {
            DefaultWebSecurityManager manager=new DefaultWebSecurityManager();
            manager.setRealm(shiroRealm);
            return manager;
        }
        //配置自定义的权限登录器
        @Bean(name="shiroRealm")
        public ShiroRealm shiroRealm(@Qualifier("credentialsMatcher") CredentialsMatcher matcher) {
            ShiroRealm shiroRealm=new ShiroRealm();
            shiroRealm.setCredentialsMatcher(matcher);
            return shiroRealm;
        }
        //配置自定义的密码比较器
        @Bean(name="credentialsMatcher")
        public CredentialsMatcher credentialsMatcher() {
            return new CredentialsMatcher();
        }
    }

    4.创建自定义的过滤器MyFilter.java,继承AccessControlFilter类,在步骤3中使用

    import org.apache.commons.lang.StringUtils;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.AccessControlFilter;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import javax.servlet.*;
    import javax.servlet.http.HttpServletRequest;
    import java.util.Enumeration;
    import java.util.Properties;
    import java.util.Set;
    
    public class MyFilter extends AccessControlFilter {
        private static Logger log = LoggerFactory.getLogger(MyFilter.class);
    
        //判断是否拦截,false为拦截,true为允许
        @Override
        public boolean isAccessAllowed(ServletRequest req, ServletResponse resp, Object object) throws Exception {
            Subject subject = getSubject(req,resp);
            String url = getPathWithinApplication(req);
            log.info("当前用户正在访问的url为 " + url);
            log.info("subject.isPermitted(url);"+subject.isPermitted(url));
            //可自行根据需要判断是否拦截,可以获得subject判断用户权限,也可以使用req获得请求头请求体信息
            //return true;
            return false;
            
        }
    
        //上面的方法返回false后(被拦截),会进入这个方法;这个方法返回false表示处理完毕(不放行);返回true表示需要继续处理(放行)
        @Override
        public boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {     
            //从req中获得的值,也可以自己使用其它判断是否放行的方法
            String username = request.getParameter("name");
            String password = request.getParameter("password");
            //创建token对象
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password);
            Subject subject = SecurityUtils.getSubject();
            try {
                //使用subject对象的login方法验证该token能否登录(使用方法2中的shiroRealm.java中的方法验证)
                subject.login(usernamePasswordToken);
            } catch (Exception e) {
                //log.info("登陆失败");
                //log.info(e.getMessage());
                return false;
            }
            //log.info("登陆成功");
            return true;
        }
    }
    

    5.由于步骤3中使用了自定义密码验证的方式,因此需要创建类CredentialsMatcher.java(与步骤3中的名称对应),继承SimpleCredentialsMatcher类

    import org.apache.shiro.authc.AuthenticationInfo;
    import org.apache.shiro.authc.AuthenticationToken;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
    
    public class CredentialsMatcher extends SimpleCredentialsMatcher {
    
        @Override
        public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
            UsernamePasswordToken utoken=(UsernamePasswordToken) token;
            //获得用户输入的密码:(可以采用加盐(salt)的方式去检验)
            String inPassword = new String(utoken.getPassword());
            //获得数据库中的密码
            String dbPassword=(String) info.getCredentials();
            //进行密码的比对
            return this.equals(inPassword, dbPassword);
        }
    }
    

    6.由于步骤3中放开了对登录页/loginController的过滤,因此我增加了一个ShiroController.java类

    import User;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.UsernamePasswordToken;
    import org.apache.shiro.subject.Subject;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.servlet.http.HttpSession;
    
    @Controller
    public class ShiroController {
        @RequestMapping("/")
        public String loginPage() {
            return "login";
        }
        @RequestMapping("/login")
        public String login() {
            return "login";
        }
        @RequestMapping("/loginController")
        public String loginUser(String username,String password,HttpSession session) {
            UsernamePasswordToken usernamePasswordToken=new UsernamePasswordToken(username,password);
            Subject subject = SecurityUtils.getSubject();
            try {
                subject.login(usernamePasswordToken);   //完成登录
                //自定义的JavaBean,用于保存用户名、密码、权限3个字段
                User user=(User) subject.getPrincipal();
                //可选,可放入session,以备后续使用
                session.setAttribute("user", user);
                //跳转到登录成功页(controller)
                return "forward:/loginSuccess";
            } catch(Exception e) {
                //登录失败,跳转回登录页(html)
                return "login";
            }
    
        }
        @RequestMapping("/logOut")
        public String logOut(HttpSession session) {
            Subject subject = SecurityUtils.getSubject();
            subject.logout();
            //session.removeAttribute("user");
            return "login";
        }
    }
    

    个人经验:

    1.shiro配置类中的url拦截的执行顺序为从上到下,如果url匹配到一个规则,则会跳出匹配方法,忽略后续的匹配规则(相当于return)。

    2.shiro使用自定义filter时,最好继承shiro的filter,不要直接继承Filter类。

    3.shiro使用自定义filter时,map集合的key配置为"authc"、value配置为"new MyFilter()"时,表示对配置为authc的url使用自定义filter进行拦截,而不会使用ShiroRealm中的验证方法验证(可能是将shiro默认的authc的拦截器覆盖了);因此最好将key配置为其它自定义的字符串,将部分url的拦截规则设置为使用自定义filter拦截即可(如果仍想使用shiro默认的拦截器,可用逗号连接"authc")。

    以上就是本文的全部内容,供大家参考,谢谢!

  • 相关阅读:
    超文本传输协议 HTTP/1.0 Hyptertext Transfer Protocol
    VB.NET中使用代表对方法异步调用
    蚂蚁解道德经(1)[转载]
    vb.net 类的属性的设置和获取问题
    VB.net入门(6):类~构造函数,事件
    什么是Ajax技术
    千里之外
    一个asp.net2005的页面文件调用CSS样式的BUG
    一个.net发送HTTP数据实体的类
    利用ASP发送和接收XML数据的处理方法
  • 原文地址:https://www.cnblogs.com/codeToSuccess/p/13906256.html
Copyright © 2020-2023  润新知