智能商贸项目记录-Shiro权限&Shiro集成Spring

Shiro:权限框架

Spring security:重量级安全框架
Apache shiro:轻量级安全框架

为什么要使用Shiro?

因为虽然重量级的功能更加强大,细粒度更高,但是它的学习难度相比较于轻量级的Shiro要高一些。虽然功能很强大,但是还是有很多地方用不到。
Shiro:粗粒度,学习难度低,功能够用。

Shiro的四大基石

身份验证(登录)
授权(权限判断)
密码学(加密)
会话管理(session:任何地方都可以使用)

宏观上来看Shiro:
使用Shiro,Subject代表当前用户。
Shiro SecurityManager:Shiro的权限管理器,Shiro的所有功能都要通过它,都是靠它来完成得分。
Realm:拿取数据全靠它。获取登录用户的信息和权限。
微观上来看Shiro:
Shiro什么语言都支持,是不是web项目也无所谓。

测试Shiro
1.拿到shiro.ini配置文件,并且拿出工厂
2、从工厂中获取到SecurityManager对象
3、把SecurityManager对象设置到上下文中
4、获取到当前用户(如果没有登录就是游客)
5、如果用户没有登录。我们就要让它登录
6、要想登录,就要先准备令牌:
7、根据令牌来实现登录。
8、退出系统。

public class HelloShiro {
    @Test
    public void myTest() throws Exception{
//        1、获取到最重要的那个对象,SecurityManager
//        2、读取到Shiro.ini的配置文件,并且拿到工厂
        Factory<SecurityManager> factory  = new IniSecurityManagerFactory("classpath:shiro.ini");
//      从工厂里面拿到SecurityManager对象
        SecurityManager securityManager = factory.getInstance();
//        把SecurityManager对象设置到上下文里面
//        将它放到一个地方,然后所有的位置都可以调用
        SecurityUtils.setSecurityManager(securityManager);
//        获取到当前用户,没有登录那就是游客访问
        Subject currentUser = SecurityUtils.getSubject();
        System.out.println("是否登录成功:"+currentUser.isAuthenticated());
//如果用户没有登录,就要让他登录
        if(!currentUser.isAuthenticated()){
            try {
//            使用户登录,先准备令牌
                UsernamePasswordToken token = new UsernamePasswordToken("guest", "guest");
//        根据令牌来实现登录
                currentUser.login(token);
            } catch (AuthenticationException e) {
                e.printStackTrace();
                System.out.println("出现了一个未知错误");
            }
        }
        //角色判断
        System.out.println("我是一个admin角色的人:"+currentUser.hasRole("admin"));
        System.out.println("我是一个it角色的人:"+currentUser.hasRole("it"));

        //权限判断
        System.out.println("我有employee:save的权限:"+currentUser.isPermitted("employee:save"));
        System.out.println("我有employee:delete的权限:"+currentUser.isPermitted("employee:delete"));
        System.out.println("我有employee:update的权限:"+currentUser.isPermitted("employee:update"));
        System.out.println("我有department:update的权限:"+currentUser.isPermitted("department:update"));

        //System.out.println("是否登录成功:"+currentUser.isAuthenticated());
        //退出系统
        //currentUser.logout();
        //System.out.println("是否登录成功:"+currentUser.isAuthenticated());
    }
}

未知账号异常
UnknownAccountException
不正确密码异常
IncorrectCredentialsException

定义一个自己的Realm,根据自己想要的效果来完成功能。

package cn.cxm.shiro;

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.apache.shiro.util.ByteSource;

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

//自定义Realm,按照自己想要的效果来完成功能
//自定义Realm一般直接继承AuthorizingRealm接口,因为里面包含了身份认证和授权两个方法
public class MyRealm extends AuthorizingRealm{
//    获取到这个Realm的名称
    @Override
    public String getName(){
        return "MyRealm";
    }

//    必须是登录成功进来的,拿到用户名,根据用户名去到数据库里面拿到对应的角色与权限
//    授权验证的方法
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //1.拿到当前用户名
        // 拿出当前登录用户的主体,也就是用户名
        String principal = (String) principalCollection.getPrimaryPrincipal();
        //2.根据用户名拿到对应的角色与权限
        Set<String> roles = getRoles(principal);
        Set<String> perms = getPerms(principal);

        //3.返回对应的对象
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roles);
        authorizationInfo.setStringPermissions(perms);

        return authorizationInfo;
    }

    //模拟根据用户名拿到对应的角色与权限
    private Set<String> getRoles(String username){
        Set<String> roles = new HashSet<String>();
        roles.add("admin");
        roles.add("it");
        return roles;
    }
    private Set<String> getPerms(String username){
        Set<String> perms = new HashSet<String>();
        perms.add("employee:save");
        perms.add("employee:delete");
        perms.add("employee:*");
        perms.add("*");
        return perms;
    }


//   登录验证(身份验证)
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //1.拿到令牌(用户名密码Token)
        UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
        //2.拿到用户名,这里的用户名是传过来的
        String username = token.getUsername();
        //3.根据用户名到数据库进行查询
        //  这个密码是数据库的密码
        String password = getByName(username);
        if(password==null){
            //返回空就是代表用户名不存在,shior会自动帮我们报UnknownAccountException
            return null;
        }
//        在这里添加盐值,通过ByteSource来添加盐值
        ByteSource salt = ByteSource.Util.bytes("cxm");
        //4.我们把值传进去,它会自己帮我们判断密码
        //  传的是数据库密码:它要把这个密码和令牌中的密码做对象,如果对应不上,就会报:IncorrectCredentialsException
//       将盐值添加到下面
        SimpleAuthenticationInfo authenticationInfo
                = new SimpleAuthenticationInfo(username,password,salt,getName());
        return authenticationInfo;
    }

    //模拟根据当前登录用户拿到密码
    private String getByName(String username){
        if("root".equals(username)){
            return "282e4ced1f2b6fe522107c5051d27450";
        }else if("guest".equals(username)){
            return "guest";
        }
        return null;
    }
}

快捷键:Ctrl+T,可以看到子类

密码加密功能

MD5加密
验证加密的密码:
使用加密凭证匹配器

//      设置Realm的密码匹配器
//        new一个密码匹配器
        HashedCredentialsMatcher matcher = new HashedCredentialsMatcher();
//        定义密码算法
        matcher.setHashAlgorithmName("MD5");
//        定义加密次数
        matcher.setHashIterations(10);
        myRealm.setCredentialsMatcher(matcher);
//      在这里我们没有办法添加盐值,需要到MyRealm里面才能添加

配置盐值

匹配器里面只有加密的方式和次数
盐值设置不设置早匹配器里面,而是设置在Realm里面。

//        在这里添加盐值,通过ByteSource来添加盐值
        ByteSource salt = ByteSource.Util.bytes("cxm");
        //4.我们把值传进去,它会自己帮我们判断密码
        //  传的是数据库密码:它要把这个密码和令牌中的密码做对象,如果对应不上,就会报:IncorrectCredentialsException
//       将盐值添加到下面
        SimpleAuthenticationInfo authenticationInfo
                = new SimpleAuthenticationInfo(username,password,salt,getName());
        return authenticationInfo;

Shiro集成Spring

把核心对象(SecurityManager)交给Spring创建,MyRealm也交给Spring创建。
将我们的aisell项目备份后导入,
然后导包。

    <!-- shiro的支持包 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-all</artifactId>
      <version>1.4.0</version>
      <type>pom</type>
    </dependency>
    <!-- shiro与Spring的集成包 -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>

在web.xml里面配置Shiro过滤器。

Shiro的过滤器可以拦截所有的请求。
DelegatingFilterProxy,这个过滤器什么功能都没有,他什么也不做,真正的过滤功能是在Spring中的过滤器里面完成的。是J2EE的原生过滤器,它将代码拦截以后,交给Spring来处理。

  <!-- Spring与shiro集成:需要定义一个shiro过滤器(这是一个代理过滤器,它会到spring的配置中找一个名称相同的真实过滤器) -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <init-param>
      <param-name>targetFilterLifecycle</param-name>
      <param-value>true</param-value>
    </init-param>
  </filter>

  <filter-mapping>
    <filter-name>shiroFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

配置applicationContext-shiro.xml,并在applicationContext.xml里面读取shiro的配置文件。
配置applicationContext-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-3.0.xsd">

    <!-- securityManager:Spring创建核心对象 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 配置一个realm,到数据库中获取权限数据 -->
        <property name="realm" ref="aiSellRealm"/>
    </bean>
    <!-- 2.我们可以自定义一个realm这个必需实现org.apache.shiro.realm.Realm接口 -->
    <bean id="aiSellRealm" class="cn.cxm.aisell.shiro.AiSellRealm">
        <property name="credentialsMatcher">
            <bean class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
                <property name="hashAlgorithmName" value="MD5"/>
                <property name="hashIterations" value="10" />
            </bean>
        </property>
    </bean>

    <!-- 3.lifecycleBeanPostProcessor:可以自动调用在Spring Ioc窗口中 Shiro bean的生成周期方法 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>
    <!-- 4.启动ioc容器中使用 shiro的注解,但是必需配置在Spring Ioc容器中Shiro bean的生成周期方法 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor"/>
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

    <!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!-- 登录的url,如果没有登录成功,你访问的路径会跳到这个页面 -->
        <property name="loginUrl" value="/s/login.jsp"/>
        <!-- 登录成功的url,如果登录成功,会跳转到这个页面 -->
        <property name="successUrl" value="/s/main.jsp"/>
        <!-- 没有权限时跳转到这个位置 -->
        <property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>
        <!--
            下面是在配置哪些资源被保护,哪些资源需要权限
            anon:不需要登录也可以访问相应的权限
            authc:需要权限才能访问
            /** :所有文件及其子文件
            路径 = perms[权限]
            /employee/index = perms[employee:index]
        -->
        <!--这里的权限是固定的,假如要放开很多路径的权限,就要写很多,不方便,所以不用-->
        <!--<property name="filterChainDefinitions">
            <value>
                /s/login.jsp = anon
                /s/main.jsp = anon
                /s/unauthorized.jsp = anon
                /** = authc
            </value>
        </property>-->
        <!--使用下面的方法-->
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMapBuilderMap" />
    </bean>
    <!--最后的结果就是对应方法返回的Map值-->
    <bean id="filterChainDefinitionMapBuilderMap" factory-bean="filterChainDefinitionMapBuilder"
          factory-method="createFilterChainDefinitionMap" />
    <!--配置相应的bean-->
    <bean id="filterChainDefinitionMapBuilder" class="cn.cxm.aisell.shiro.FilterChainDefinitionMapBuilder" />
</beans>

<!--引入shiro的配置-->
    <import resource="classpath:applicationContext-shiro.xml"/>

web.xml里面配置的shiro过滤器名字必须和applicationContext-shiro.xml里面的真正的shiro过滤器名字一样。

 <!-- 5.shiro的真实过滤器(注:这个名称必需和web.xml的代表过滤器【DelegatingFilterProxy】名称一样) -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!-- 登录的url,如果没有登录成功,你访问的路径会跳到这个页面 -->
        <property name="loginUrl" value="/s/login.jsp"/>
        <!-- 登录成功的url,如果登录成功,会跳转到这个页面 -->
        <property name="successUrl" value="/s/main.jsp"/>
        <!-- 没有权限时跳转到这个位置 -->
        <property name="unauthorizedUrl" value="/s/unauthorized.jsp"/>

获取Map过滤

package cn.cxm.aisell.shiro;

import java.util.LinkedHashMap;

//这个方法是用来定义权限的,哪些路径放行,哪些路径不放行,在这里来写
public class FilterChainDefinitionMapBuilder {
//使用集合,将需要放行的路径,需要权限的路径,需要登录访问的路径放进去
    public LinkedHashMap<Object, Object> createFilterChainDefinitionMap(){
        LinkedHashMap<Object, Object> filterChainDefinitionMap = new LinkedHashMap<>();
//        设置不需要访问也可以被放行的路径
        filterChainDefinitionMap.put("/s/login.jsp", "anon");
        filterChainDefinitionMap.put("/login", "anon");
        //需要权限才能访问的路径
        filterChainDefinitionMap.put("/employee/index", "perms[employee:index]");
//        设置需要登录才能被放行的路径
        filterChainDefinitionMap.put("/**", "authc");

        return filterChainDefinitionMap;
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值