SSH入门项目-6-Shiro安全框架

Shiro安全框架

百度一下:

Apache Shiro(读作“sheeroh”,即日语“城”)是一个开源安全框架,提供身份验证、授权、密码学和会话管理。Shiro框架直观、易用,同时也能提供健壮的安全性。

这里写图片描述

  • Authentication:身份认证/登录,验证用户是不是拥有相应的身份;
  • Authorization:授权,即权限验证,验证某个已认证的用户是否拥有某个权限;即判断用户是否能做事情,常见的如:验证某个用户是否拥有某个角色。或者细粒度的验证某个用户对某个资源是否具有某个权限;
  • Session Manager:会话管理,即用户登录后就是一次会话,在没有退出之前,它的所有信息都在会话中;会话可以是普通JavaSE环境的,也可以是如Web环境的;
  • Cryptography:加密,保护数据的安全性,如密码加密存储到数据库,而不是明文存储;

Shiro安全框架交互过程:

这里写图片描述

  • Subject:主体,代表了当前“用户”,这个用户不一定是一个具体的人,与当前应用交互的任何东西都是Subject,如网络爬虫,机器人等;即一个抽象概念;所有Subject都绑定到SecurityManager,与Subject的所有交互都会委托给SecurityManager;可以把Subject认为是一个门面;SecurityManager才是实际的执行者;
  • SecurityManager:安全管理器;即所有与安全有关的操作都会与SecurityManager交互;且它管理着所有Subject;可以看出它是Shiro的核心,它负责与后边介绍的其他组件进行交互,如果学习过SpringMVC,你可以把它看成DispatcherServlet前端控制器;
  • Realm:域,Shiro从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;可以把Realm看成DataSource,即安全数据源。

程序员关注2个部分:如果获得Subject与如何定义一个符合规定的Realm(包括密码比较器)。

Shiro安全框架过滤器配置:

过滤器简称对应的java类
anonorg.apache.shiro.web.filter.authc.AnonymousFilter
authcorg.apache.shiro.web.filter.authc.FormAuthenticationFilter
authcBasicorg.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter
permsorg.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter
portorg.apache.shiro.web.filter.authz.PortFilter
restorg.apache.shiro.web.filter.authz.HttpMethodPermissionFilter
rolesorg.apache.shiro.web.filter.authz.RolesAuthorizationFilter
sslorg.apache.shiro.web.filter.authz.SslFilter
userorg.apache.shiro.web.filter.authc.UserFilter
logoutorg.apache.shiro.web.filter.authc.LogoutFilter

Shiro标签:

标签名称标签条件(均是显示标签内容)
shiro:authenticated登录之后
shiro:notAuthenticated不在登录状态时
shiro:guest用户在没有RememberMe时
shiro:user用户在RememberMe时
shiro:hasAnyRoles name=”abc,123”在有abc或者123角色时
shiro:hasRole name=”abc”拥有角色abc
shiro:lacksRole name=”abc”没有角色abc
shiro:hasPermission name=”abc”拥有权限资源abc
shiro:lacksPermission name=”abc”没有abc权限资源
shiro:principal默认显示用户名称

使用Shiro

1,maven坐标的配置

这里写图片描述
在parent的工程中已经配置好了。

2,配置过滤器(放到struts的配置之前)

    <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>

3,在spring applicationContext.xml中记载shiro配置文件,放在事务管理器之前配置

这里就是产生代理的方式,应该放在事务代理器之前

<!--2.Shiro安全框架产生代理子类的方式: 使用cglib方式 -->
    <aop:aspectj-autoproxy proxy-target-class="true" />

4,导入Shiro的配置文件

<?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:p="http://www.springframework.org/schema/p"  
    xmlns:context="http://www.springframework.org/schema/context"   
    xmlns:tx="http://www.springframework.org/schema/tx"  
    xmlns:aop="http://www.springframework.org/schema/aop"  
    xsi:schemaLocation="http://www.springframework.org/schema/beans    
    http://www.springframework.org/schema/beans/spring-beans.xsd    
    http://www.springframework.org/schema/aop    
    http://www.springframework.org/schema/aop/spring-aop.xsd    
    http://www.springframework.org/schema/tx    
    http://www.springframework.org/schema/tx/spring-tx.xsd    
    http://www.springframework.org/schema/context    
    http://www.springframework.org/schema/context/spring-context.xsd">

    <description>Shiro的配置</description>

    <!-- SecurityManager配置 -->
    <!-- 配置Realm域 -->
    <!-- 密码比较器 -->
    <!-- 代理如何生成? 用工厂来生成Shiro的相关过滤器-->
    <!-- 配置缓存:ehcache缓存 -->
    <!-- 安全管理 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- Single realm app.  If you have multiple realms, use the 'realms' property instead. -->
        <property name="realm" ref="authRealm"/><!-- 引用自定义的realm -->
        <!-- 缓存 -->
        <property name="cacheManager" ref="shiroEhcacheManager"/>
    </bean>

    <!-- 自定义权限认证 -->
    <bean id="authRealm" class="cn.itcast.jk.shiro.AuthRealm">
        <property name="userService" ref="userService"/>
        <!-- 自定义密码加密算法  -->
        <property name="credentialsMatcher" ref="passwordMatcher"/>
    </bean>

    <!-- 设置密码加密策略 md5hash -->
    <bean id="passwordMatcher" class="cn.itcast.jk.shiro.CustomCredentialsMatcher"/>

    <!-- filter-name这个名字的值来自于web.xml中filter的名字 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--登录页面  -->
        <property name="loginUrl" value="/index.jsp"></property>
        <!-- 登录成功后 -->      
        <property name="successUrl" value="/home.action"></property>
        <property name="filterChainDefinitions">
            <!-- /**代表下面的多级目录也过滤 -->
            <value>
                /index.jsp* = anon
                /home* = anon
                /sysadmin/login/login.jsp* = anon
                /sysadmin/login/logout.jsp* = anon
                /login* = anon
                /logout* = anon
                /components/** = anon
                /css/** = anon
                /images/** = anon
                /js/** = anon
                /make/** = anon
                /skin/** = anon
                /stat/** = anon
                /ufiles/** = anon
                /validator/** = anon
                /resource/** = anon
                /** = authc
                /*.* = authc
            </value>
        </property>
    </bean>

    <!-- 用户授权/认证信息Cache, 采用EhCache  缓存 -->
    <bean id="shiroEhcacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache-shiro.xml"/>
    </bean>

    <!-- 保证实现了Shiro内部lifecycle函数的bean执行 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 生成代理,通过代理进行控制 -->
    <bean class="org.springframework.aop.framework.autoproxy.DefaultAdvisorAutoProxyCreator"
          depends-on="lifecycleBeanPostProcessor">
        <property name="proxyTargetClass" value="true"/>
    </bean>

    <!-- 安全管理器 -->
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

</beans>

做一点简单的描述吧。
在shiro的配置文件中,需要我我们手动处理的几个部分。

  • 安全管理器的配置
    需要注入我们自己的验证域和一个缓存管理。缓存管理比较简单,验证域应该就是我们比较和新的验证业务。
    这里写图片描述

  • 缓存管理的内容如下,这个路径是需要我们手动更改的。
    这里写图片描述

  • 自定义的认证域配置
    这里写图片描述
    我们需要注入一个是用户的service,另一个是我们的密码比较器。
    我们看到出了注入下方的bean就是我们自定义的密码比较器。

5,先配置一些必要的注入等内容:

5.1,先导入缓存管理的文件

我们之前看到上图中的缓存管理注入的位置是“classpath:ehcache-shiro.xml”,所以我们就在resource中直接创建这个文件

<?xml version="1.0" encoding="UTF-8"?>
<ehcache updateCheck="false" name="shiroCache">

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            />
</ehcach

5.2,密码比较器

先用一个密码比较器的空模板,包的位置看一下注入的property

public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {
    public boolean doCredentialsMatch(AuthenticationToken token,
            AuthenticationInfo info) {
        return false;

    }
}

5.3,AuthRealm的注入set方法。

public class AuthRealm extends AuthorizingRealm{

    public UserService userService;
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
        // TODO Auto-generated method stub
        return null;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
        // TODO Auto-generated method stub
        return null;
    }


}

6,在Spring中导入Shiro的配置文件

<import resource="classpath:applicationContext-shiro.xml"></import> 

运行起来之后,点击部门管理会变成下面这个死样子
这里写图片描述

原因就是部门管理不能直接访问了,需要转到登录页,但是我们的系统目前没有登录功能,就只能回到这个首页了。
很难得,一个错误的页面验证了之前的工作都是正确的。保存一下草稿,丢了N回了。

解决一下,在fmain.jsp中添加一个script

<script type="text/javascript">
    if (self.location != top.location) {
        top.location = self.location;
    }
</script>

解释一下,由于shiro过滤器告诉失败会让我们的页面回到index.jsp,而我们顶层框架是部门管理。于是我们通过上述代码让顶层框架访问index.jsp。其原因是我们现在没有配合登录页面。
于是我们回到LoginAction类中,看到我们之前注释掉的代码,做一点修改。

public String login() throws Exception {

        // if(true){
        // String msg = "登录错误,请重新填写用户名密码!";
        // this.addActionError(msg);
        // throw new Exception(msg);
        // }
        // User user = new User(username, password);
        // User login = userService.login(user);
        // if (login != null) {
        // ActionContext.getContext().getValueStack().push(user);
        // session.put(SysConstant.CURRENT_USER_INFO, login); //记录session
        // return SUCCESS;
        // }
        // return "login";

        if (UtilFuns.isEmpty(username)) {
            return "login";
        }

        return SUCCESS;
    }

再看一下。
这里写图片描述

7,补充登录功能

7.1,密码比较器编写

7.1.1,导入md5hash工具类

在util中添加Encrypt工具类。

public class Encrypt {
    /*
     * 散列算法一般用于生成数据的摘要信息,是一种不可逆的算法,一般适合存储密码之类的数据,
     * 常见的散列算法如MD5、SHA等。一般进行散列时最好提供一个salt(盐),比如加密密码“admin”,
     * 产生的散列值是“21232f297a57a5a743894a0e4a801fc3”,
     * 可以到一些md5解密网站很容易的通过散列值得到密码“admin”,
     * 即如果直接对密码进行散列相对来说破解更容易,此时我们可以加一些只有系统知道的干扰数据,
     * 如用户名和ID(即盐);这样散列的对象是“密码+用户名+ID”,这样生成的散列值相对来说更难破解。
     */

    //高强度加密算法,不可逆
    public static String md5(String password, String salt){
        return new Md5Hash(password,salt,2).toString();
    }
}

7.1.2,密码比较器的编写

public class CustomCredentialsMatcher extends SimpleCredentialsMatcher {

    // token:用户在页面输入的用户名密码,info代表从密码中得到的加数据
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        // 向下转型
        UsernamePasswordToken usertoken = (UsernamePasswordToken) token;

        // 将用户输入的原始密码加密
        // 注意token.getPassword()拿到的是一个char[],不能直接用toString(),它底层实现不是我们想的直接字符串,只能强转
        // 用户名做为salt
        Object tokenCredentials = Encrypt.md5(String.valueOf(usertoken.getPassword()), usertoken.getUsername());

        //取得数据库中的密码数据
        Object accountCredentials = getCredentials(info);

         return equals(tokenCredentials, accountCredentials);

    }

}

这里的使用的equals是父类提供的。
这里写图片描述

7.2,认证域的编写

package com.liuyang19900520.liussh.shiro;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
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 com.liuyang19900520.liussh.domain.Module;
import com.liuyang19900520.liussh.domain.Role;
import com.liuyang19900520.liussh.domain.User;
import com.liuyang19900520.liussh.service.UserService;

public class AuthRealm extends AuthorizingRealm {

    // 注入
    public UserService userService;

    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    // 授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection arg0) {
        System.out.println("授权");
        // 首先根据name找到对象,对象的next就是一个user
        User user = (User) arg0.fromRealm(this.getName()).iterator().next();// 根据realm的名字去找对应的realm。

        Set<Role> roles = user.getRoles();// 兑现导航
        List<String> permissions = new ArrayList<>();

        for (Role role : roles) {
            Set<Module> moudles = role.getModules();
            for (Module m : moudles) {
                // 这些标签在shiro:hasPermission的标签中会出现重复,
                // 但是标签的操作时用hascontents方法来进行,所以有重复也没有关系
                permissions.add(m.getName());
            }
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addStringPermissions(permissions);// 添加用户的权限--即我们的模块
        return info;
    }

    // 认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken arg0) throws AuthenticationException {
        System.out.println("认证");

        // 向下转型(代表用户用在页面上输入的用户名与密码)

        UsernamePasswordToken upToken = (UsernamePasswordToken) arg0;

        // 调用业务方法,根据业务名查询
        String hql = "from User where userName=?";
        List<User> list = userService.find(hql, User.class, new String[] { upToken.getUsername() });

        if (list != null && list.size() > 0) {
            User user = list.get(0);
            AuthenticationInfo info = new SimpleAuthenticationInfo(user, user.getPassWord(), this.getName());
            return info;// 此处如果返回,就会进入到密码比较器。
        }

        return null;
    }

}

7.3,编写loginAction写登录逻辑

// SSH传统登录方式
    public String login() throws Exception {

        if (UtilFuns.isEmpty(username)) {
            return "login";
        }

        try {
            // 得到Subject
            Subject subject = SecurityUtils.getSubject();

            // 构造token(我们的username和password是页面上的)
            UsernamePasswordToken upToken = new UsernamePasswordToken(username, password);

            // 盗用shiro中的登录方法
            subject.login(upToken);// 当这一行代码执行时,就会自动跳转到比吗比较器

            // shiro有缓存,用户信息存在缓存中,
            User user = (User) subject.getPrincipal();

            // 将User放入sessoin中
            session.put(SysConstant.CURRENT_USER_INFO, user);

        } catch (Exception e) {
            request.put("errorInfo", "对不起,用户名密码错误");
            return "login";
        }

        return SUCCESS;
    }

8,顶部菜单释放

这里写图片描述

如果你也想问,为什么左侧的菜单不用加shiro标签么,那么请看一下左侧菜单的代码。
这里写图片描述
如图所示也就是说我们后续进到的模块都是通过user的角色到数据库查询出来的,不在需要shiro标签进行授权了,就是说菜单都是动态的。
所以我们要解开这个
这里写图片描述

还有值得注意的一点是,一旦JSP页面上出现shiro标签,就会执行授权方法。即认证域中的doGetAuthorizationInfo方法。

日常改错

输入正确的用户密码都不好使啊。这下验证得实在是太好了。
于是我重启了一下服务,JRbel真的不好使啊。于是成功了。
这里写图片描述
这里写图片描述

虽然权限看起来不同了,可是菜单怎么突然变多了这么多,并且还是重复的,一定是哪里出了问题。我们再来查询一下。
结果是清一下浏览器缓存,应该就没有问题了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值