如何把shiro集成到spring中?

说明:

1.本文主要描述的是一个web项目中shiro的大致执行流程,以及把shiro与spring相互集成的流程。

2.本文将从搭建简单的web项目架构开始,来描述项目的整个执行过程。

3.web项目的整个执行流程:当项目发布到tomcat的时候,tomcat会先加载web.xml文件,然后解析xml,在这个xml中有提供服务的另外一个xml,有提供安全框架shiro的xml,有sping相关的xml,有项目基本信息的配置文件等。由于在web.xml中含有其他的xml,因此程序会进入该xml进行解析,知道所有项解析完毕,跳出该xml,再次进入web.xml。(Servlet 在启动时,会自动查找WEB-IN日下的applicationContext. xml 文件,其他文件也将会自动加载)。当进入页面时,用户通过表单提交数据之后,会进入提交的路径(也就是控制层中),然后通过控制转发到具体的业务层,执行业务逻辑。

4.在xml中当我们想去使用某一个对象的信息,我们不是手动的创建一个,而是通过sping IOC去要一个,同时也可以通过xml的方式修改该对象中的一些属性。

5.为了便于控制对象的创建与清理,因此常常把自定义的对象也让IOC去托管。


Shiro的基本信息

1.什么是shiro?

答:是 Java 的一个安全(权限)框架.

2.它有什么作用呢?

答:认证、授权、加密、会话管理、与Web 集成、缓存等。

3.该如何去使用它?

 答:Shiro 提供了与 Web 集成的支持,其通过一个 ShiroFilter 入口来拦截需要安全控制的URL,然后 进行相应的控制。

4.具体执行的流程是什么?(可参考下面的代码)

1.tomcat加载spring、shiro、servlect、数据库、系统相关的xml文件。
   1.1 tomcate首先访问的到的是web.xml,然后启动servlect,加载它里面的xml以及页面。
   1.2 某些xml会向spring IOC容器注入实体,以及加载其他的配置文件。

2.出现一个表单提交页面,进行用户数据的提交。
   2.1 该页面是经过web.xml配置实现调度的。

3.控制层用来接收用户传入的用户名与密码信息,然后调用当前的Subject的登录方法,将生成的token传入实现AuthorizingRealm这个接口的ShiroRealm中。
    3.1 Controller首先获取当前的Subject
    3.2 测试当前用户是否已经登录
    3.3 如果没有登录,记录用户名与密码,生成token对象
    3.4 遇到其他的异常情况可以选择对应的异常抛出。

4.在ShiroRealm类中重写doGetAuthenticationInfo(登录验证)与doGetAuthorizationInfo(授权验证)方法。
    4.1 首先调用 Subject.login(token) 进行登录,其会自动委托给SecurityManager
    4.2 SecurityManager 负责真正的身份验证逻辑;它会委托给Authenticator 进行身份验证;
    4.3 Authenticator 才是真正的身份验证者,Shiro API 中核心的身份认证入口点,此处可以自定义插入自己的实现;
    4.4 Authenticator 可能会委托给相应的 AuthenticationStrategy 进行多 Realm 身份验证,默认 ModularRealmAuthenticator 会调用
AuthenticationStrategy 进行多 Realm 身份验证;
    4.5 Authenticator 会把相应的 token 传入 Realm,从 Realm 获取身份验证信息,如果没有返回/抛出异常表示身份验证失败了。此处可以配置多个Realm,将按照相应的顺序及策略进行访问。
   

注意事项 :用到的类-详细解释

  1. shiroFilter

配置时必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean.
配置哪些页面需要受保护.
以及访问这些页面需要的权限.
1). anon 可以被匿名访问 说明不需要认证
2). authc 必须认证(即登录)后才可能访问的页面.
3). logout 登出.
4). roles 角色过滤器
<property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
             </value>
        </property>

  2. realm

 Shiro 从 Realm 获取安全数据(如用户、角色、 权限),即 SecurityManager 要验证用户身份,那么它需 要从 Realm 获取相应的用户进行比较以确定用户身份是否 合法;也需要从Realm得到用户相应的角色/权限进行验证 用户是否能进行操作.

//当前对象的Realm,用于用户名与密码校验(与数据库中存储是数据进行校验)。
String realmName = getName();

 

 

5.spring集成shiro的搭建过程(具体可参考给出的部分源码)

        1. 首先加入Spring、shiro相关的jar包

          

        2.配置在web.xml

          这个文件就是tomcat首先加载的页面,这个页面中主要集成了访问页面的路径、servlet服务、将要加载的其他xml、shiro。

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

    <!--应用程序上下文(就是一些实体信息)  将配置文件的基本信息引入web项目中-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:applicationContext.xml</param-value>
    </context-param>

    <!-- 在servlet初始化之前引导根Web应用程序上下文  -->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>

    <!-- 这个SpringWeb应用程序的前控制器,负责处理所有应用程序请求 -->
    <servlet>
        <servlet-name>spring</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>

    <!-- 将所有请求映射到Servlet进行处理-->
    <servlet-mapping>
        <servlet-name>spring</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    <!--
    1. 配置  Shiro 的 shiroFilter. 拦截入口
    2. DelegatingFilterProxy 实际上是 Filter 的一个代理对象. 默认情况下, Spring 会到 IOC 容器中查找和
    <filter-name> 对应的 filter bean. 也可以通过 targetBeanName 的初始化参数来配置 filter bean 的 id.如果
    targetBeanName的id是 abc,那么IOC配置文件也是要用abc,否者就是无法联系上下文的异常。
    -->
    <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>
</web-app>

3.配置applicationContext.xml

    这个文件主要是集成的sping相关的配置、缓存工具、Realm、servlet服务文件以及shiro的核心shoirFilter.

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

    <!--
    1. 配置 SecurityManager!(缓冲管理器)
    -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="cacheManager" ref="cacheManager"/>
        <property name="realms" ref="jdbcRealm"/>
    </bean>

    <!-- Let's use some enterprise caching support for better performance.  You can replace this with any enterprise
         caching framework implementation that you like (Terracotta+Ehcache, Coherence, GigaSpaces, etc -->
    <!--
    2. 配置 CacheManager.(可以使用企业的缓冲根据)
    2.1 需要加入 ehcache 的 jar 包及配置文件.
    -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <!-- Set a net.sf.ehcache.CacheManager instance here if you already have one.  If not, a new one
             will be creaed with a default config:
             <property name="cacheManager" ref="ehCacheManager"/> -->
        <!-- If you don't have a pre-built net.sf.ehcache.CacheManager instance to inject, but you want
             a specific Ehcache configuration to be used, specify that here.  If you don't, a default
             will be used.: -->
        <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"/>
    </bean>

    <!--
    	3. 配置 Realm
    	3.1 直接配置实现了 org.apache.shiro.realm.Realm 接口的 bean,然后放到sping IOC容器中
    -->
    <bean id="jdbcRealm" class="com.zskj.java.realms.ShiroRealm"></bean>

    <!-- Post processor that automatically invokes init() and destroy() methods
         for Spring-configured Shiro objects so you don't have to
         1) specify an init-method and destroy-method attributes for every bean
            definition and
         2) even know which Shiro objects require these methods to be
            called. -->
    <!--
    4. 配置 LifecycleBeanPostProcessor. 可以自定的来调用配置在 Spring IOC 容器中 shiro bean 的生命周期方法.
    -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- Enable Shiro Annotations for Spring-configured beans.  Only run after
         the lifecycleBeanProcessor has run: -->
    <!--
    5. 启用 IOC 容器中使用 shiro 的注解. 但必须在配置了 LifecycleBeanPostProcessor 之后才可以使用.
    -->
    <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>

      <!--
    6. 配置 ShiroFilter.
    6.1 id 必须和 web.xml 文件中配置的 DelegatingFilterProxy 的 <filter-name> 一致.
                      若不一致, 则会抛出: NoSuchBeanDefinitionException. 因为 Shiro 会来 IOC 容器中查找和 <filter-name> 名字对应的 filter bean.
    -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <property name="loginUrl" value="/login.jsp"/>
        <property name="successUrl" value="/list.jsp"/>
        <property name="unauthorizedUrl" value="/unauthorized.jsp"/>
        <!--
              配置哪些页面需要受保护.
              以及访问这些页面需要的权限.
              1). anon 可以被匿名访问 说明不需要认证
              2). authc 必须认证(即登录)后才可能访问的页面.
              3). logout 登出.
              4). roles 角色过滤器
          -->
        <property name="filterChainDefinitions">
            <value>
                /login.jsp = anon
                /list.jsp = anon
                /shiro/logout= logout
                /shiro/login = anon
            </value>
        </property>
    </bean>
</beans>
<?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:mvc="http://www.springframework.org/schema/mvc"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
		http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
		http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd">
    <!--扫描项目业务逻辑包-->
    <context:component-scan base-package="com.zskj.java"></context:component-scan>
    <!--配置web工程启动服务,调用页面-->
    <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
        <!--前缀名-->
        <property name="prefix" value="/"></property>
        <!--后缀名-->
        <property name="suffix" value=".jsp"></property>
    </bean>
    <!--mvc设计模式-->
    <mvc:annotation-driven></mvc:annotation-driven>
    <mvc:default-servlet-handler/>

</beans>

4.最后配置常用的缓存配置文件,IOC的相关实体,这些实体是在源码中调用了。

5.添加访问页面,进行测试框架是否搭建无bug.

6.实现用户的认证以及授权逻辑代码。该实体是已经注入到IOC容器中的,因此只需要去实现就行了。当调用login()方法的时候值自动去执行该文件。就是实现了AuthorizingRealm 方法,因为返回值是AuthenticationInfo 返回的是它的实现类SimpleAuthenticationInfo.

package com.atguigu.shiro.realms;

import java.util.HashSet;
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.LockedAccountException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.crypto.hash.SimpleHash;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;

public class ShiroRealm extends AuthorizingRealm {

	@Override
	protected AuthenticationInfo doGetAuthenticationInfo(
			AuthenticationToken token) throws AuthenticationException {
		System.out.println("[FirstRealm] doGetAuthenticationInfo");
		
		//1. 把 AuthenticationToken 转换为 UsernamePasswordToken 
		UsernamePasswordToken upToken = (UsernamePasswordToken) token;
		
		//2. 从 UsernamePasswordToken 中来获取 username
		String username = upToken.getUsername();
		
		//3. 调用数据库的方法, 从数据库中查询 username 对应的用户记录
		System.out.println("从数据库中获取 username: " + username + " 所对应的用户信息.");
		
		//4. 若用户不存在, 则可以抛出 UnknownAccountException 异常
		if("unknown".equals(username)){
			throw new UnknownAccountException("用户不存在!");
		}
		
		//5. 根据用户信息的情况, 决定是否需要抛出其他的 AuthenticationException 异常. 
		if("monster".equals(username)){
			throw new LockedAccountException("用户被锁定");
		}
		
		//6. 根据用户的情况, 来构建 AuthenticationInfo 对象并返回. 通常使用的实现类为: SimpleAuthenticationInfo
		//以下信息是从数据库中获取的.
		//1). principal: 认证的实体信息. 可以是 username, 也可以是数据表对应的用户的实体类对象. 
		Object principal = username;
		//2). credentials: 密码. 
		Object credentials = null; //"fc1709d0a95a6be30bc5926fdb7f22f4";
		if("admin".equals(username)){
			credentials = "038bdaf98f2037b31f1e75b5b4c9b26e";
		}else if("user".equals(username)){
			credentials = "098d2c478e9c11555ce2823231e02ec1";
		}
		
		//3). realmName: 当前 realm 对象的 name. 调用父类的 getName() 方法即可
		String realmName = getName();
		//4). 盐值. 
		ByteSource credentialsSalt = ByteSource.Util.bytes(username);
		
		SimpleAuthenticationInfo info = null; //new SimpleAuthenticationInfo(principal, credentials, realmName);
		info = new SimpleAuthenticationInfo(principal, credentials, credentialsSalt, realmName);
		return info;
	}

	public static void main(String[] args) {
		String hashAlgorithmName = "MD5";
		Object credentials = "123456";
		Object salt = ByteSource.Util.bytes("user");;
		int hashIterations = 1024;
		
		Object result = new SimpleHash(hashAlgorithmName, credentials, salt, hashIterations);
		System.out.println(result);
	}

	//授权会被 shiro 回调的方法
	@Override
	protected AuthorizationInfo doGetAuthorizationInfo(
			PrincipalCollection principals) {
		//1. 从 PrincipalCollection 中来获取登录用户的信息
		Object principal = principals.getPrimaryPrincipal();
		
		//2. 利用登录的用户的信息来用户当前用户的角色或权限(可能需要查询数据库)
		Set<String> roles = new HashSet<>();
		roles.add("user");
		if("admin".equals(principal)){
			roles.add("admin");
		}
		
		//3. 创建 SimpleAuthorizationInfo, 并设置其 reles 属性.
		SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roles);
		
		//4. 返回 SimpleAuthorizationInfo 对象. 
		return info;
	}
}

 

 


 

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值