SSM与SpringBoot整合shiro

1、SSM整合shiro

1.1 添加依赖

和之前的SSM相比,整合shiro的时候需要添加一个依赖

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

shiro-spring这个依赖包含了shiro-core与shiro-web,添加之后就不再需要添加shiro-core和shiro-web了。

1.2 spring-shiro.xml配置

spring-shiro.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--配置shiro的过滤器工厂-->
    <bean class="org.apache.shiro.spring.web.ShiroFilterFactoryBean" id="shiroFilter">
        <!--设置securityManager-->
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/index.jsp"/>
        <!-- override these for application-specific URLs if you like:
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="/home.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/> -->
        <!-- The 'filters' property is not necessary since any declared javax.servlet.Filter bean  -->
        <!-- defined will be automatically acquired and available via its beanName in chain        -->
        <!-- definitions, but you can perform instance overrides or name aliases here if you like: -->
        <!-- <property name="filters">
            <util:map>
                <entry key="anAlias" value-ref="someFilter"/>
            </util:map>
        </property> -->
        <!--配置url与权限-->
        <property name="filterChainDefinitions">
            <value>
                /index.jsp=anon
                /main.jsp=authc
                /manager.jsp=roles[manager]
                /guest.jsp=roles[guest]
            </value>
        </property>
    </bean>
    <!--配置默认的webSecurityManager-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
        <!--自定义自己的realm-->
        <property name="realm" ref="myRealm"/>
        <!-- By default the servlet container sessions will be used.  Uncomment this line
             to use shiro's native sessions (see the JavaDoc for more): -->
        <!-- <property name="sessionMode" value="native"/> -->
    </bean>
    <bean id="myRealm" class="com.qianfeng.shiro.MyRealm"/>
    <bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor" id="lifecycleBeanPostProcessor"/>
</beans>

这个xml文件配置了四个bean:ShiroFilterFactoryBean、DefaultWebSecurityManager、MyRealm、LifecycleBeanPostProcessor。ShiroFilterFactoryBean的作用是配置过滤器工厂,id要和稍后要说的web.xml中的shiro过滤器的名称一样,指定securityManager,等于页面,过滤页面。DefaultWebSecurityManager这个的主要作用就是配置realm,如果有多个realm就要配置realms,MyRealm是我们自定义的Realm,然后LifecycleBeanPostProcessor是一个AOP的实现。

1.3 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <!--配置过滤器,这个过滤器的名字应该和spring-shiro.xml中过滤器的id一样-->
    <filter>
        <filter-name>shiroFilter</filter-name>
        <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
        <!--
            将过滤器的生命周期从出生到死亡完全交给Spring来管理
        -->
        <init-param>
            <param-name>targetFilterLifecycle</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:*.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!-- Make sure any request you want accessible to Shiro is filtered. /* catches all -->
    <!-- requests.  Usually this filter mapping is defined first (before all others) to -->
    <!-- ensure that Shiro works in subsequent filters in the filter chain:             -->
    <filter-mapping>
        <filter-name>shiroFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--    spring写好的中文过滤器-->
    <filter>
        <filter-name>encode</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>encode</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>
</web-app>

和shiro相关的就是一个过滤器和一个监听器,这个过滤器类是DelegatingFilterProxy,name应该和spring-shiro.xml中ShiroFilterFactoryBean的id一样,监听器是ContextLoaderListener,文本加载监听器,context-param这个标签将classpath目录下的所有xml文件读取进来。

1.4 MyRealm

MyReanl.java

package com.qianfeng.shiro;

import com.qianfeng.entity.Employee;
import com.qianfeng.entity.Permission;
import com.qianfeng.entity.Roles;
import com.qianfeng.service.IEmployeeService;
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 javax.annotation.Resource;
import java.util.List;

/**
 * @author huwen
 */
public class MyRealm extends AuthorizingRealm {

    @Resource
    private IEmployeeService employeeService;
    /**
     * 授权方法
     * @param principalCollection principal的集合,可以理解为各种用户身份的集合,比如用户名、邮箱、手机号等
     * @return 返回的是授权信息,包括角色与权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //SimpleAuthorizationInfo是AuthorizationInfo的子类
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //得到可用的principal,然后转换为字符串
        String empName = getAvailablePrincipal(principalCollection).toString();
        //调用service
        List<Roles> roles = employeeService.getAllRolesByEmpName(empName);
        for (Roles role : roles) {
            info.addRole(role.getRoleName());
        }
        List<Permission> permissions = employeeService.getAllPermissionsByEmpName(empName);
        for (Permission permission : permissions) {
            info.addStringPermission(permission.getPermName());
        }
        return info;
    }

    /**
     * 这个方法用于认证
     * @param authenticationToken 用户名与密码
     * @return 认证信息
     * @throws AuthenticationException 可能引发的异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        AuthenticationInfo info = null;
        //将authenticationToken强转为usernamePasswordToken,向下转型能够成功因为我们知道用户输入的是用户名与密码
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //从token中获取用户名与密码传给service,最后交给dao从数据库中查询
        String username = token.getUsername();
        //使用字节数组存储密码更为安全,因为字节数组是可变的,字符串是不可变的存在常量池中
        char[] password = token.getPassword();
        String pass = new String(password);
        Employee emp = employeeService.getEmployeeByNameAndPassword(username,pass);
        //如果查询到的数据不为空,就构造一个SimpleAuthenticationInfo对象,将用户名与密码放在里里面
        if(emp!=null && emp.getEmpId()!=0){
            //getName()获取到的是当前Realm的标识,因为可以自定义多个Realm,不同的Realm需要区分
            info = new SimpleAuthenticationInfo(username,pass,getName());
        }
        return info;
    }
}

MyRealm继承自AuthorizingRealm,重写两个方法,第一个方法用于授权,第二个方法用于认证。

1.5 EmployeeController

package com.qianfeng.controller;

import com.qianfeng.entity.Employee;
import com.qianfeng.service.IEmployeeService;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PostMapping;

import javax.annotation.Resource;

@Controller
public class EmployeeController {
    @Resource
    private IEmployeeService employeeService;
    @PostMapping("/login")
    public String empLogin(Employee employee){
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(employee.getEmpName(),employee.getPassword());
        try {
            subject.login(token);
            return "main";
        } catch (AuthenticationException e) {
            e.printStackTrace();
        }
        return "index";
    }
}

可以看到和之前的web项目的不同,不需要我们手动创建配置工厂生成实例了。

2、SpringBoot整合shiro

2.1 添加依赖

这里shiro需要的依赖和SSM项目中的依赖一样,都是shiro-spring。如果想要在thymeleaf中使用shiro标签,需要添加下面的依赖:

 <dependency>
     <groupId>com.github.theborakompanioni</groupId>
     <artifactId>thymeleaf-extras-shiro</artifactId>
     <version>2.0.0</version>
</dependency>

然后载html标签中添加xmlns:shiro="http://www.pollix.at/thymeleaf/shiro"。

2.2 MyRealm

package com.qianfeng.shiro;

import com.qianfeng.entity.Employee;
import com.qianfeng.entity.Permission;
import com.qianfeng.entity.Roles;
import com.qianfeng.service.IEmployeeService;
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.stereotype.Component;

import javax.annotation.Resource;
import java.util.List;

/**
 * @author huwen
 */
@Component("myRealm")
public class MyRealm extends AuthorizingRealm {

    @Resource
    private IEmployeeService employeeService;
    /**
     * 授权方法
     * @param principalCollection principal的集合,可以理解为各种用户身份的集合,比如用户名、邮箱、手机号等
     * @return 返回的是授权信息,包括角色与权限
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //SimpleAuthorizationInfo是AuthorizationInfo的子类
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        //得到可用的principal,然后转换为字符串
        String empName = getAvailablePrincipal(principalCollection).toString();
        //调用service
        List<Roles> roles = employeeService.getAllRolesByEmpName(empName);
        for (Roles role : roles) {
            info.addRole(role.getRoleName());
        }
        List<Permission> permissions = employeeService.getAllPermissionsByEmpName(empName);
        for (Permission permission : permissions) {
            info.addStringPermission(permission.getPermName());
        }
        return info;
    }

    /**
     * 这个方法用于认证
     * @param authenticationToken 用户名与密码
     * @return 认证信息
     * @throws AuthenticationException 可能引发的异常
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        AuthenticationInfo info = null;
        //将authenticationToken强转为usernamePasswordToken,向下转型能够成功因为我们知道用户输入的是用户名与密码
        UsernamePasswordToken token = (UsernamePasswordToken) authenticationToken;
        //从token中获取用户名与密码传给service,最后交给dao从数据库中查询
        String username = token.getUsername();
        //使用字节数组存储密码更为安全,因为字节数组是可变的,字符串是不可变的存在常量池中
        char[] password = token.getPassword();
        String pass = new String(password);
        Employee emp = employeeService.getEmployeeByNameAndPassword(username,pass);
        //如果查询到的数据不为空,就构造一个SimpleAuthenticationInfo对象,将用户名与密码放在里里面
        if(emp!=null && emp.getEmpId()!=0){
            //getName()获取到的是当前Realm的标识,因为可以自定义多个Realm,不同的Realm需要区分
            info = new SimpleAuthenticationInfo(username,pass,getName());
        }
        return info;
    }
}

MyRealm这个类和SSM项目基本一样,唯一的不同是加上了@Component("myRealm")这个注解

2.3 ShiroConfig

ShiroConfig.java

package com.qianfeng.config;

import com.qianfeng.shiro.MyRealm;
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.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.HashMap;
import java.util.Map;

@Configuration
public class ShiroConfig {
    /**
     * 该方法用于返回过滤器工厂
     * @param securityManager
     * @return
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilterFactoryBean(@Qualifier("defaultWebSecurityManager")DefaultWebSecurityManager securityManager){
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        Map<String,String> map = new HashMap<>(10);
        map.put("/index.html","anon");
        map.put("/main.html","authc");
        shiroFilterFactoryBean.setLoginUrl("/login.html");
        shiroFilterFactoryBean.setUnauthorizedUrl("/unauth.html");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(map);
        return shiroFilterFactoryBean;
    }

    /**
     * 设置realm为自定义的Realm,返回securityManager
     * @param realm
     * @return
     */
    @Bean(name = "defaultWebSecurityManager")
    public DefaultWebSecurityManager defaultWebSecurityManager(@Qualifier("myRealm")MyRealm realm){
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);
        return securityManager;
    }

    /**
     * 开启shiro的注解,需要借助SpringAOP扫描Shiro注解的类,来进行安全校验
     * @return
     */
    @Bean
    public DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator(){
        DefaultAdvisorAutoProxyCreator advisorAutoProxyCreator = new DefaultAdvisorAutoProxyCreator();

        advisorAutoProxyCreator.setProxyTargetClass(true);
        return advisorAutoProxyCreator;
    }

    /**
     * 开启aop的注解支持
     * @param defaultWebSecurityManager
     * @return
     */
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(DefaultWebSecurityManager defaultWebSecurityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();

        authorizationAttributeSourceAdvisor.setSecurityManager(defaultWebSecurityManager);

        return  authorizationAttributeSourceAdvisor;
    }
}

由于springboot不使用xml文件进行配置,所以采用Java类的方式对shiro进行设置,整个类上有一个注解@Configuration,共有四个方法每个方法都有一个@Bean注解,方法名可以自定义,但是参数与返回值必须对应,第一个方法的作用是设置并返回过滤器工厂,第二个方法的作用是设置securityManager并返回,下面那两个方法都是和AOP相关的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容