spring和shiro的集成,以及web项目的登陆权限验证

一 Shrio与Spring集成

  • Shiro的理论
    • shiro是一个权限框架(身份认证,授权,会话,密码学)
    • shiro(轻量级,粗粒度,其它涉及到细粒度自己代码完成)与Spring security(重量级,细粒度,有点麻烦)
    • 身份认证(登录)authentication,授权authorization,密码学,会话管理
      Subject(当前用户)—》SecurityMananger(所有功能都经过它)–》Realm(获取真实数据的一个组件)

二 2.2.shiro能干什么?

在这里插入图片描述Shiro 开发团队称为“应用程序的四大基石” ——身份验证,授权,会话管理和加密作为其目标。
Authentication(身份认证):
有时也简称为“登录”,这是一个证明用户是他们所说的他们是谁的行为。
Authorization(授权):
访问控制的过程,也就是绝对“谁”去访问“什么”权限。
Session Management:管理用户特定的会话,即使在非 Web 或 EJB 应用程序。
Cryptography:通过使用加密算法保持数据安全同时易于使用。
也提供了额外的功能来支持和加强在不同环境下所关注的方面,尤其是以下这些:
Web Support: Shiro 的 web 支持的 API 能够轻松地帮助保护 Web 应用程序。
Caching:缓存是 Apache Shiro 中的第一层公民,来确保安全操作快速而又高效。
Concurrency: Apache Shiro 利用它的并发特性来支持多线程应用程序。
Testing:测试支持的存在来帮助你编写单元测试和集成测试,并确保你的能够如预期的一样安全。
“Run As”:一个允许用户假设为另一个用户身份(如果允许)的功能,有时候在管理脚本很有用。
“Remember Me”:在会话中记住用户的身份,所以他们只需要在强制时候登录。
在这里插入图片描述在这里插入图片描述

三 身份验证

Authentication(身份认证)

  • 引入
    在集成Spring的例子中,只有登录页面是不需要登录可以直接访问。其他的页面需要认证(即登录)后才能访问,那怎么来实现登录认证,又叫做身份认证呢?

  • 概念
    在这里插入图片描述

做认证需要用户名和密码
认证流程:
在这里插入图片描述

详细步骤分析
要想实现登录,分前台和后台两部分:
前台:
实现一个login.jsp的页面,用来搜集登录信息(用户名和密码)!当点击登录时把登录信息提交到后台完成认证。
后台:
写一个Controller接收前台传入的登录信息,完成登录认证。
具体步骤如下:
1)创建LoginConroller,写一个方法接收前台登录请求并接受登录信息(用户名和密码)
2)获取当前的 Subject. 调用 SecurityUtils.getSubject();
3)测试当前的用户是否已经被认证. 即是否已经登录. 调用 Subject 的 isAuthenticated()
4)若没有被认证, 则把用户名和密码封装为 UsernamePasswordToken 对象
5)执行登录: 调用 Subject 的 login(AuthenticationToken) 方法.
6)自定义 Realm 的方法, 从数据库中获取对应的记录, 返回给 Shiro.
入门中使用的是Shiro自带的IniRealm,IniRealm从ini配置文件中读取用户的信息,大部分情况下需要从系统的数据库中读取用户信息,所以需要自定义realm。
实际上需要继承 org.apache.shiro.realm.AuthenticatingRealm 类
实现 doGetAuthenticationInfo(AuthenticationToken) 方法.
7)由 shiro 完成对密码的比对.

四 Shiro入门

前提:创建一个普通Maven项目

  • 导包
<dependencies>
    <dependency>
        <groupId>org.apache.shiro</groupId>
        <artifactId>shiro-core</artifactId>
        <version>1.4.0</version>
    </dependency>
    <dependency>
        <groupId>commons-logging</groupId>
        <artifactId>commons-logging</artifactId>
        <version>1.2</version>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

五 集成spring

  1. 为什么要集成Spring
    我们的项目基本都是通过Spring来管理bean的,如果要想使用Shiro,那就要把shiro集成到Spring。集成Spring的核心就是把框架的核心类(SecurityManager,Subject,Realm)交给Spring管理!
  2. 集成的准备
    准备Spring-web项目
    3.web.xml配置
<!-- 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>
  1. 在applicationContext.xml中引入applicationContext-shiro.xml让其交给spring管理
  2. 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">
       
    <!-- 1.配置apache的管理器  引用我们自己的realm 创建时自动注入  -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 配置一个realm,到数据库中获取权限数据 -->
        <property name="realm" ref="myRealm"/>
    </bean>
    
    <!--相当于自动创建核心管理器的时候注入我们自己的realm对象-->
    <!-- 2.我们可以自定义一个realm【暂时未实现功能】这个必需实现org.apache.shiro.realm.Realm接口 -->
    <bean id="myRealm" class="com.lirui.web.shiro.AiSellRealm">
    </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:需要登陆才能访问
              /** :所有文件及其子文件
        -->
        <property name="filterChainDefinitions">
            <value>
               <!-- /s/login.jsp = anon
                /login = anon
                /dept/index = perms[dept:index]
                /employee/index = perms[employee:index]
                /** = authc-->
            </value>
        </property>
        <property name="filterChainDefinitionMap" ref="filterChainDefinitionMap"></property>
    </bean>
    <!-- 这个bean是帮助咱们获取相应的值:它会到一个工厂bean中通过对应的方法拿到相应的值 -->
    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory" factory-method="builderFilterChainDefinitionMap"></bean>
    <!-- 配置可以创建 -->
    <bean id="filterChainDefinitionMapFactory" class="com.lirui.web.shiro.FilterChainDefinitionMapFactory">
    </bean>

</beans>
  • 注意
  • 为什么要自定义Realm
  • 配置真实过滤器的细节,路径需要
  • 为什么要创建一个FilterChainDefinitionMapFactory类来配置获取url和权限的key和value
  • 配置哪些资源被保护,哪些资源需要权限
    anon:不需要登录也可以访问相应的权限
    authc:需要登陆才能访问
    /** :所有文件及其子文件

六 自定义Reaml

为了让需求自己控制,所以自定义Realm

  • 获取数据的组件
  • 登陆验证,授权
public class AiSellRealm extends AuthorizingRealm {
    //获取到这个Realm的名称(随便取)
    @Override
    public String getName() {
        return "MyRealm";
    }
    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        //拿到主角。。登陆方法验证传过来的是什么  这里就是什么。。
        String username = (String)principalCollection.getPrimaryPrincipal();
        //应该从数据库中查出。。这里是假设
        Set<String> roles = getRoles(username);
        Set<String> permissions = getPermissions();
        //核心管理器会调用doGetAuthorizationInfo方法得到这个用户的权限和角色
        //设置到权限的对象并且返回
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.setRoles(roles);
        authorizationInfo.setStringPermissions(permissions);
        //返回权限对象
        return authorizationInfo;
    }

    public Set<String> getRoles(String username){
        Set<String> set = new HashSet<String>();
        //set.add("admin");
        set.add("it");
        return set;
    }

    public Set<String> getPermissions(){
        Set<String> set = new HashSet<String>();
        set.add("employee:index");
        //set.add("dept:*");
        return set;
    }

    //登陆验证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        //先拿用户传过来的令牌
         UsernamePasswordToken token = (UsernamePasswordToken)authenticationToken;
         //拿到令牌的用户
        String username = token.getUsername();
        String password = getByName(username);
        if (password==null){
            return null;
        }
        //加盐验证
        //要想使用,还需要在xml配置加密次数,和加密算法。。才可以正确解密验证
        ByteSource salt = ByteSource.Util.bytes("itsource");
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,password,salt,getName());
        return simpleAuthenticationInfo;
    }
    //模拟从数据库查出来的对应用户名的密码
    public String getByName(String usernmae){
        if ("root".equals(usernmae)){
            return "admin";
        }else if("lirui".equals(usernmae)) {
            return "123456";
        }
        return null;
    }

}

  • 自定义需要继承extends AuthorizingRealm,覆写两个方法,登陆和授权的不同返回验证对象
  • 登陆验证,用户传进来的token令牌,查询数据库密码,没有查到返回null说明用户名错误
//有用户和密码则   返回  验证对象
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,password,salt,getName());
  • realm只是查询到数据,返回验证对象,真正的验证是核心管理器接管

七 几个测试的跳转页面

  1. /s/login.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
<form action="/login" method="post">
    用户名: <input type="text" name="username" />
    密码: <input type="text" name="password" />
    <input type="submit"  value="登陆">
</form>
</body>
</html>
  1. /s/main.jsp 登陆成功的页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
欢迎光临
</body>
</html>

  1. /s/unauthorized.jsp 用户已经登陆但是授权失败的页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
没有访问权限不好意思
</body>
</html>

八 controller层

@Controller
public class ShiroController {
    @RequestMapping("/login")
    public String login(String username,String password){
        System.out.println("===========================jll");
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, password);
        if (!subject.isAuthenticated()){
            try {
                subject.login(token);
            } catch (UnknownAccountException e) {
                System.out.println("用户名不存在");
                e.printStackTrace();
            }
            catch (IncorrectCredentialsException e) {
                System.out.println("密码错误");
                e.printStackTrace();
            }
            catch (AuthenticationException e) {
                System.out.println("未知错误");
                e.printStackTrace();
            }
        }
        //如果登陆验证失败,要访问首页还是会被拦截下来
        return "redirect:/s/main.jsp";
    }
}

九 FilterChainDefinitionMapFactory类

创建这个类的原因是,如果有很多页面的权限管理,比如
/s/login.jsp = anon
/login = anon
/dept/index = perms[dept:index]
/employee/index = perms[employee:index]
不可能全写在xml配置中,应该写出来,放在对象中,所以新建一个类创建map
把需要过滤的路径和登陆授权的需要信息以key和value的形式存入
注意:因为过滤器的顺序是从上往下,所以map需要顺序,我们使用
LinkedHashMap
代码示例

public class FilterChainDefinitionMapFactory {

public LinkedHashMap<String,String>  builderFilterChainDefinitionMap(){
 LinkedHashMap<String, String> linkedHashMap = new LinkedHashMap<>();
 /*
 /s/login.jsp = anon
 /login = anon
 /dept/index = perms[dept:index]
 /employee/index = perms[employee:index]
 /** = authc
  * */
 //anon  不需要登陆就可以访问   authc需要登陆才可以访问  per[xx:xx]需要授权才可以
 linkedHashMap.put("/s/login.jsp", "anon");
 linkedHashMap.put("/login", "anon");
 linkedHashMap.put("/dept/index", "perms[dept:index]");
 linkedHashMap.put("/employee/index", "perms[employee:index]");
 linkedHashMap.put("/**", "authc");
 return linkedHashMap;
}
}

  • 注意xml配置
 <!-- 这个bean是帮助咱们获取相应的值:它会到一个工厂bean中通过对应的方法拿到相应的值 -->
    <bean id="filterChainDefinitionMap" factory-bean="filterChainDefinitionMapFactory" factory-method="builderFilterChainDefinitionMap"></bean>
    <!-- 配置可以创建 -->
    <bean id="filterChainDefinitionMapFactory" class="com.lirui.web.shiro.FilterChainDefinitionMapFactory">

执行流程分析

  • 发生了什么?
    用户想要登陆或者访问其他页面了
  • 我们后台怎么响应?
    首先拦截下来,根据xml的配置信息,拦截并且跳转到登陆页面
  • 输入密码用户信息点击登陆过后?
    来到controller层,需要进行验证,spring创建shiro核心管理器,用户信息封装为UsernamePasswordToken,核心管理器拿到我们自定义的realm,根据token查询username的密码,如果没有查到,返回null,用户有错
    如果查到了密码,将username,password,如果有加盐,一并封装为验证对象
SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(username,password,salt,getName());

如果登陆成功
可以访问没有权限的页面,
如果登陆失败,返回登陆页面,跳转其他页面失败,还是会验证,因为没有登陆成功
若有权限的页面不能访问
需要访问时,核心管理器继续判断,通过realm的授权方法继续验证授权
并且将查询的授权和角色信息存入simpleAuthenticationInfo 对象返回出去。
如果验证成功,信息是存在与shiro的session中,该session远比http的session强大,什么系统都能用

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值