美文网首页
整合 SpringMVC & Shiro

整合 SpringMVC & Shiro

作者: 索伦x | 来源:发表于2019-06-04 23:43 被阅读0次

    测试数据

    准备测试数据及相应的测试页面

    工程中定义几个地址:
    /gologin.html 不需要权限验证就可以访问
    /login.html 不需要验证就可以访问
    /doadd.html 要有perm1和perm2权限才可以访问,访问成功后页面显示add
    /doget.html 要有admin权限才可以访问,访问成功后页面显示get
    /doupdate.html 要有perm1权限才能访问,访问成功后页面显示update
    /dodel.html 要有perm2权限才可以访问,访问成功后页面显示del

    添加依赖

    <!--shiro核心类库-->
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.2.3</version>
    </dependency>
    <!--日志工具包-->
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
        <version>1.6.1</version>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-api</artifactId>
        <version>1.6.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.32</version>
    </dependency>
    <!--spring相关包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>4.3.11.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>4.3.11.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>4.3.11.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-spring</artifactId>
        <version>1.4.0</version>
    </dependency>
    

    配置文件

    db.properties

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/数据库?characterEncoding=utf-8
    jdbc.username=用户名
    jdbc.password=密码
    

    log4j.properties

    log4j.rootLogger=INFO, stdout, 
    
    log4j.appender.stdout=org.apache.log4j.ConsoleAppender
    log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
    log4j.appender.stdout.layout.ConversionPattern=%d %p [%c] - %m%n
    

    spring-context.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
    
        <!--读取配置文件-->
        <context:property-placeholder location="classpath:db.properties" ignore-unresolvable="true"/>
        <!--从配置文件中获取数据源-->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="${jdbc.driver}"/>
            <property name="url" value="${jdbc.url}"/>
            <property name="username" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
        </bean>
        <!-- 配置Jdbc模板 -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <bean id="shiroDAO" class="dao.ShiroDAO">
            <property name="jdbcTemplate" ref="jdbcTemplate"/>
        </bean>
        <bean id="shiroService" class="service.ShiroService">
            <property name="shiroDAO" ref="shiroDAO"/>
        </bean>
        <bean id="myShiroRealm" class="util.MyShiroRealm">
            <property name="shiroService" ref="shiroService"/>
        </bean>
    
        <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
            <property name="realm" ref="myShiroRealm"/>
        </bean>
        <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
            <property name="securityManager" ref="securityManager"/>
            <!--去登录的地址-->
            <property name="loginUrl" value="/gologin.html"/>
            <!--登录成功的跳转地址-->
            <property name="successUrl" value="/index.html"/>
            <!--验证失败的跳转地址-->
            <property name="unauthorizedUrl" value="/error.jsp"/>
            <!--定义过滤的规则-->
            <!--复杂的系统中,url和权限都可以从数据库中读取-->
            <!--anon是不需要验证,authc时需要验证,perms[admin]代表要admin权限-->
            <property name="filterChainDefinitions">
                <value>
                    /gologin.html = anon
                    /login.html = anon
                    /doadd.html = authc, perms[perm1,perm2]
                    /doget.html = authc, perms[admin]
                    /doupdate.html = authc, perms[perm1]
                    /dodel.html = authc, perms[perm2]
                </value>
            </property>
        </bean>
    </beans>
    

    配置说明
    shiro过滤器过滤属性含义:

    • securityManager:这个属性是必须的。
    • loginUrl :没有登录的用户请求需要登录的页面时自动跳转到登录页面,不是必须的属性,不输入地址的话会自动寻找项目web项目的根目录下的”/login.jsp”页面。
    • successUrl :登录成功默认跳转页面,不配置则跳转至”/”。如果登陆前点击的一个需要登录的页面,则在登录自动跳转到那个需要登录的页面。不跳转到此。
    • unauthorizedUrl :没有权限默认跳转的页面
      其权限过滤器及配置释义:

    anon:
    例子/admins/=anon 没有参数,表示可以匿名使用。
    authc:
    例如/admins/user/
    =authc表示需要认证(登录)才能使用,没有参数
    roles(角色)
    例子/admins/user/=roles[admin],参数可以写多个,参数之间用逗号分割,当有多个参数时,例如admins/user/=roles["admin,guest"],每个参数通过才算通过,相当于hasAllRoles()方法。
    perms(权限)
    例子/admins/user/=perms[add],参数可以写多个,例如/admins/user/=perms["add, modify"],当有多个参数时必须每个参数都通过才通过,想当于isPermitedAll()方法。
    rest
    例子/admins/user/=rest[user],根据请求的方法,相当于/admins/user/=perms[user:method] ,其中method为post,get,delete等。
    port
    例子/admins/user/=port[8081],当请求的url的端口不是8081是跳转到schemal://serverName:8081?queryString,其中schmal是协议http或https等,serverName是你访问的host,8081是url配置里port的端口,queryString
    是你访问的url里的?后面的参数。
    authcBasic
    例如/admins/user/
    =authcBasic没有参数.表示httpBasic认证
    ssl:
    例子/admins/user/=ssl没有参数,表示安全的url请求,协议为https
    user:
    例如/admins/user/
    =user没有参数表示必须存在用户,当登入操作时不做检查
    spring-mvc.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
      http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
      http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.2.xsd">
        <context:annotation-config />
        <!-- 启动自动扫描 -->
        <context:component-scan base-package="controller">
            <!-- 制定扫包规则 ,只扫描使用@Controller注解的JAVA类 -->
            <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
        </context:component-scan>
    </beans>
    

    web.xml

        <!-- 配置shiro的核心拦截器 -->
        <filter>
            <filter-name>shiroFilter</filter-name>
            <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        </filter>
        <filter-mapping>
            <filter-name>shiroFilter</filter-name>
            <url-pattern>*.html</url-pattern>
        </filter-mapping>
    

    DAO层

    package dao;
    
    import org.springframework.jdbc.core.JdbcTemplate;
    
    import java.util.List;
    
    public class ShiroDAO {
        private JdbcTemplate jdbcTemplate;
    
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        /**
         * 根据用户名查询密码
         */
        public String getPasswordByUserName(String username) {
            String sql = "select PASSWORD from SHIRO_USER where USER_NAME = ?";
            String password = jdbcTemplate.queryForObject(sql, String.class, username);
            return password;
        }
    
        /**
         * 查询当前用户对应的权限
         */
        public List<String> getPermissionByUserName(String username) {
            String sql = "select P.PERM_NAME from SHIRO_ROLE_PERMISSION P inner join SHIRO_USER_ROLE R on P.ROLE_NAME=R.ROLE_NAME where R.USER_NAME = ?";
            List<String> perms = jdbcTemplate.queryForList(sql, String.class, username);
            return perms;
        }
    }
    

    Service层

    package service;
    
    import com.sun.scenario.effect.impl.sw.sse.SSEBlend_SRC_OUTPeer;
    import dao.ShiroDAO;
    import org.apache.shiro.SecurityUtils;
    import org.apache.shiro.authc.*;
    import org.apache.shiro.subject.Subject;
    
    import java.util.List;
    
    public class ShiroService {
        private ShiroDAO shiroDAO;
    
        public void setShiroDAO(ShiroDAO shiroDAO) {
            this.shiroDAO = shiroDAO;
        }
    
        /**
         * 登录
         */
        public void doLogin(String username, String password) throws Exception {
            Subject currentUser = SecurityUtils.getSubject();
            if (!currentUser.isAuthenticated()) {
                UsernamePasswordToken token =
                        new UsernamePasswordToken(username, password);
                token.setRememberMe(true);//是否记住用户
                try {
                    currentUser.login(token);//执行登录
                } catch (UnknownAccountException uae) {
                    throw new Exception("账户不存在");
                } catch (IncorrectCredentialsException ice) {
                    throw new Exception("密码不正确");
                } catch (LockedAccountException lae) {
                    throw new Exception("用户被锁定了 ");
                } catch (AuthenticationException ae) {
                    ae.printStackTrace();
                    throw new Exception("未知错误");
                }
            }
        }
    
        /**
         * 根据用户名查询密码
         */
        public String getPasswordByUserName(String username) {
            return shiroDAO.getPasswordByUserName(username);
        }
    
        /**
         * 查询用户所有权限
         */
        public List<String> getPermissionByUserName(String username) {
            return shiroDAO.getPermissionByUserName(username);
        }
    }
    

    注:rememberMe后浏览器里会生成一个cookie:

    如果访问的路径,要求权限是user,所有使用过rememberMe的用户就都可以访问。但是它只是记录你登录过,不会记住你是谁以及你的权限信息。

    SpringMVC Controller

    package controller;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.servlet.ModelAndView;
    import service.ShiroService;
    
    @Controller
    public class ShiroController {
        @Autowired
        private ShiroService shiroService;
    
        @RequestMapping("/gologin.html")
        public String goLogin() {
            return "/login.jsp";
        }
    
        @RequestMapping("/login.html")
        public ModelAndView login(String username, String password) {
            try {
                shiroService.doLogin(username, password);
            } catch (Exception e) {
                return new ModelAndView("/error.jsp", "msg", e.getMessage());
            }
            return new ModelAndView("/index.jsp");
        }
        @RequestMapping("/logout.html")
        public String logout() {
            Subject currentUser = SecurityUtils.getSubject();
            currentUser.logout();
            return "/login.jsp";
        }
    
        /**
         * 模拟不同的请求,在配置文件中对请求进行权限拦截
         */
        @RequestMapping("/do{act}.html")
        public ModelAndView get(@PathVariable String act) {
            //简化代码,省略数据库操作
            //通过页面上显示的信息查看请求是否被拦截
            return new ModelAndView("/page.jsp", "page", act);
        }
    }
    

    自定义Realm

    package util;
    
    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 service.ShiroService;
    
    import java.util.List;
    
    public class MyShiroRealm extends AuthorizingRealm {
        private ShiroService shiroService;
    
        public void setShiroService(ShiroService shiroService) {
            this.shiroService = shiroService;
        }
    
        @Override
        protected AuthorizationInfo doGetAuthorizationInfo
                     (PrincipalCollection principalCollection) {
            //根据自己的需求编写获取授权信息,这里简化代码获取了用户对应的所有权限
            String username = 
            (String) principalCollection.fromRealm(getName()).iterator().next();
            if (username != null) {
                List<String> perms = shiroService.getPermissionByUserName(username);
                if (perms != null && !perms.isEmpty()) {
                    SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
                    for (String each : perms) {
                        //将权限资源添加到用户信息中
                        info.addStringPermission(each);
                    }
                    return info;
                }
            }
            return null;
        }
    
        @Override
        protected AuthenticationInfo doGetAuthenticationInfo
            (AuthenticationToken authenticationToken) throws AuthenticationException {
            UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
            // 通过表单接收的用户名,调用currentUser.login的时候执行
            String username = token.getUsername();
            if (username != null && !"".equals(username)) {
                //查询密码
                String password = shiroService.getPasswordByUserName(username);
                if (password != null) {
                    return new SimpleAuthenticationInfo(username, password, getName());
                }
            }
            return null;
        }
    }
    

    测试

    准备相应的页面:略

    动态配置过滤规则

    在实际开发中,url和对应的访问权限可能需要从数据库中读取,我们可以定义一个工具类从数据库中读取访问权限并传递给Shiro。

    package util;
    
    import org.apache.shiro.config.Ini;
    import org.apache.shiro.util.CollectionUtils;
    import org.apache.shiro.web.config.IniFilterChainResolverFactory;
    import org.springframework.beans.factory.FactoryBean;
    
    import java.text.MessageFormat;
    import java.util.HashMap;
    import java.util.LinkedHashSet;
    import java.util.Map;
    import java.util.Set;
    
    public class MyChainDefinitions implements FactoryBean<Ini.Section> {
        public static final String PREMISSION_STRING = "perms[{0}]";
        public static final String ROLE_STRING = "roles[{0}]";
        private String filterChainDefinitions;
    
        public void setFilterChainDefinitions(String filterChainDefinitions) {
            this.filterChainDefinitions = filterChainDefinitions;
        }
    
    
        @Override
        public Ini.Section getObject() throws Exception {
            //urls可以从数据库查出来,此处省略代码,直接模拟几条数据
            Set<String> urls = new LinkedHashSet<>();
            urls.add("/dotest1.html");
            urls.add("/dotest2.html");
            //每个url对应的权限也可以从数据库中查出来,这里模拟几条数据
            Map<String, String[]> permsMap = new HashMap<>();
            permsMap.put("/dotest1.html", new String[]{"perm1", "admin"});
            permsMap.put("/dotest2.html", new String[]{"perm1"});
    
            //加载配置默认的过滤链
            Ini ini = new Ini();
            ini.load(filterChainDefinitions);
            Ini.Section section = ini.getSection(IniFilterChainResolverFactory.URLS);
            if (CollectionUtils.isEmpty(section)) {
                section = ini.getSection(Ini.DEFAULT_SECTION_NAME);
            }
            for (String url : urls) {
                String[] perms = permsMap.get(url);
                StringBuilder permFilters = new StringBuilder();
                for (int i = 0; i < perms.length; i++) {
                    permFilters.append(perms[i]).append(",");
                }
                //去掉末尾的逗号
                String str = permFilters.substring(0, permFilters.length() - 1);
                //生成结果如:/dotest1.html = authc, perms[admin]
                section.put(url, MessageFormat.format(PREMISSION_STRING, str));
            }
            return section;
        }
    
        @Override
        public Class<?> getObjectType() {
            return this.getClass();
        }
    
        @Override
        public boolean isSingleton() {
            return false;
        }
    }
    

    注:section中是以Map存放的数据,所以放入相同的key,后放的会覆盖先放的数据。

    spring-context.xml

    <!--声明自定义规则-->
    <bean id="myChainDefinitions" class="util.MyChainDefinitions">
        <!--静态的条件-->
        <property name="filterChainDefinitions">
            <value>
                /gologin.html = anon
                /login.html = anon
                /doadd.html = authc, perms[perm1,perm2]
                /doget.html = authc, perms[admin]
                /doupdate.html = authc, perms[perm1]
                /dodel.html = authc, perms[perm2]
                /logout.html=user
            </value>
        </property>
    </bean>
    

    将shiroFilter中的filterChainDefinitions替换掉:

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/gologin.html"/>
        <property name="successUrl" value="/index.html"/>
        <property name="unauthorizedUrl" value="/error.jsp"/>
        <!--定义过滤的规则-->
        <property name="filterChainDefinitionMap" ref="myChainDefinitions"/>
    </bean>
    

    可以访问/dotest1.html和/dotest2.html查看拦截效果。

    重写过滤器

    /doadd.html = authc,perms[perm1,perm2]

    shiro默认的拦截是要满足所有的条件,但有时我们只要满足其中的一个,用于拥有perm1或perm2任何一个条件就可以访问/doadd.html。这时我们可以重写过滤器,将and变成or

    package util;
    
    import javax.servlet.ServletRequest;
    import javax.servlet.ServletResponse;
    
    import org.apache.shiro.subject.Subject;
    import org.apache.shiro.web.filter.authz.AuthorizationFilter;
    
    public class MyShiroPermFilter extends AuthorizationFilter {
        @Override
        protected boolean isAccessAllowed
                (ServletRequest req, ServletResponse resp, Object mappedValue) 
                     throws Exception {
            Subject subject = getSubject(req, resp);
            String[] permsArray = (String[]) mappedValue;
    
            if (permsArray == null || permsArray.length == 0) { //没有权限限制
                return true;
            }
            for (int i = 0; i < permsArray.length; i++) {
                //如果是角色,就是subject.hasRole()
                //若当前用户是permsArray中的任何一个,则有权限访问
                if (subject.isPermitted(permsArray[i])) {
                    return true;
                }
            }
            return false;
        }
    }
    

    spring-context.xml
    在id=”shiroFilter”的bean中加入过滤拦截:

    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/gologin.html"/>
        <property name="successUrl" value="/index.html"/>
        <property name="unauthorizedUrl" value="/error.jsp"/>
        <property name="filterChainDefinitionMap" ref="myChainDefinitions"/>
        <!--修改后的过滤规则,从and变成or-->
        <property name="filters">
            <map>
                <entry key="perms">
                    <bean class="util.MyShiroPermFilter"/>
                </entry>
            </map>
        </property>
    </bean>
    
    rememberMe属性

    rememberMe可以在浏览器中设置cookie,在spring配置中可以设置cookie的属性,如过期时间、cookie名字、加密的秘钥等:

    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMeShiro"/><!-- 浏览器中cookie的名字 -->
        <property name="httpOnly" value="true"/><!--document对象中就看不到cookie了-->
        <property name="maxAge" value="2592000"/><!-- 30天 -->
    </bean>
    
    <!-- rememberMe管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <!--秘钥要16位,24位或32位的Base64。这个解密后是1234567890abcdef-->
        <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('MTIzNDU2Nzg5MGFiY2RlZg==')}"/>
        <property name="cookie" ref="rememberMeCookie"/>
    </bean>
    <!--  在securityManager中加入rememberMe中加入配置: -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="myShiroRealm"/>
        <!--加入rememberMe的设置-->
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>
    

    配置说明:
    HttpOnly属性:
    浏览器中通过document.cookie可以获取cookie属性,设置了HttpOnly=true,在脚本中就不能的到cookie了。可以避免cookie被盗用。

    相关文章

      网友评论

          本文标题:整合 SpringMVC & Shiro

          本文链接:https://www.haomeiwen.com/subject/jsydxctx.html