Shiro之与spring整合

生活中,最使人疲惫的往往不是道路的遥远,而是心中的郁闷;最使人痛苦的往往不是生活的不幸,而是希望的破灭;最使人颓废的往往不是前途的坎坷,而是自信的丧失;最使人绝望的往往不是挫折的打击,而是心灵的死亡。所以我们要有自己的梦想,让梦想的星光指引着我们走出落漠,走出惆怅,带着我们走进自己的理想。

导读:本篇文章讲解 Shiro之与spring整合,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com,来源:原文

Shiro与Spring整合

1.web.xml配置shiroFilter

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee">

  <!-- 加载 spring 的配置文件 -->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring.xml</param-value>
  </context-param>

  <!-- 配置监听器 -->
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- 配置spring-MVC -->
  <servlet>
    <servlet-name>springmvcservlet</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>
    <load-on-startup>1</load-on-startup>
  </servlet>
  <servlet-mapping>
    <servlet-name>springmvcservlet</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!--DelegatingFilterProxy 作用是自动到 spring 容器查找名字为 shiroFilter(filter-name)的 bean 并把所有 Filter 的操作委托给它,ShiroFilter 需要配置到 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>
  
</web-app>

2.自定义realm

public class ShiroRealm extends AuthorizingRealm {

    @Override
    public String getName() {
        return "ShiroRealm";
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //从token中 获取用户身份信息
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());

       //查询数据库校验账号密码
       ......
       
        //返回认证信息由父类AuthenticatingRealm进行认证
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                username, password,getName());

        return simpleAuthenticationInfo;
    }

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //角色授权
        Set<String> roles = new HashSet<>();
        roles.add("role1");
        roles.add("role2");

        //资源授权
        Set<String> permission = new HashSet<>();
        permission.add("user_add");
        permission.add("user_select");

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.setStringPermissions(permission);
        info.setRoles(roles);
        return info;
    }

}

3.添加spring-shiro.xml配置文件

	<!-- 配置自定义 realm -->
    <bean id="userRealm" class="cn.ybzy.shiro.security.ShiroRealm"></bean>
    
	  <!-- 配置安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
    </bean>    
    
    <!-- 配置Shiro 的Web过滤器 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,这个属性是必须的 -->
        <property name="securityManager" ref="securityManager"/>
       <!-- 登录地址 -->
        <property name="loginUrl" value="/login"/>
        <!-- 权限认证失败,则跳转到指定页面 -->
        <property name="unauthorizedUrl" value="/nopermission"/>
        <!--登陆成功-->
        <property name="successUrl" value="/home"/>
        <property name="filters">
            <map>
            //添加自定义拦截
               <!-- <entry key="cross" value-ref="crossFilter"/>
                <entry key="authc" value-ref="tokenUserFilter"/>-->
               <!-- <entry key="authc" value-ref="formAuthenticationFilter" />-->
            </map>
        </property>
        <!-- Shiro连接约束配置,即过滤链的定义 -->
        <property name="filterChainDefinitions">
            <value>
           		//anon 匿名访问
                /login = anon
                //退出拦截,请求logout.action执行退出操作
                /logout = logout
                //静态资源放行
                /js/** anon
                /images/** anon
                /styles/** anon
                //authc 需要认证登录
                /** = authc
                //roles[XX]表示有XX角色才可访问
            </value>
        </property>
    </bean>

    <!-- 基于Form表单的身份验证过滤器,不配置将也会注册此过虑器,表单中的用户账号、密码及loginurl将采用默认值,建议配置 -->
   <!--  <bean id="formAuthenticationFilter" class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">
        <!-- 表单中账号的input名称 -->
        <property name="usernameParam" value="username" />
        <!-- 表单中密码的input名称 -->
        <property name="passwordParam" value="password" />
        <!-- <property name="rememberMeParam" value="rememberMe"/> -->
        <!-- loginurl:用户登陆地址,此地址是可以http访问的url地址 -->
        <property name="loginUrl" value="/login" />
    </bean>-->

4.编写登录方法

 @RequestMapping("/login")
    public String login(Model model, HttpServletRequest req) throws  Exception{
    
	 String errorClassName = (String)req.getAttribute("shiroLoginFailure");
	 if(errorClassName !=null){
        if(UnknownAccountException.class.getName().equals(errorClassName)) {
            req.setAttribute("error", "用户名/密码错误");
        } else if(IncorrectCredentialsException.class.getName().equals(errorClassName)) {
            req.setAttribute("error", "用户名/密码错误");
        } else if(errorClassName != null) {
            req.setAttribute("error", "未知错误:" + errorClassName);
        }
       }
 		//不处理登录成功(认证成功),shiro会自动跳转到上一个请求路径
 		//失败会到login.jsp       
        req.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(req, resp);
        return "forward:/login.jsp";
    }

Shiro授权

一、授权方式

1.编程式:

通过写 if/else 授权代码块完成

必须进入方法中才能判断是否有权限
Subject subject = SecurityUtils.getSubject();
if(subject.hasRole(“admin”)) {
    //有权限
} else {
    //无权限
}

2.注解式:

通过在执行的 Java 方法上放置相应的注解完成

可以在请求进入方法之前进行权限控制
@RequiresRoles("admin")
public void hello() {
    //有权限
}
没有权限将抛出相应的异常;需在spring-shiro中配置

3.JSP/GSP 标签:

虽然页面没有显式的请求按钮,但是可以通过请求地址访问
<shiro:hasRole name="admin">
<!— 有权限 —>
</shiro:hasRole>

二、配置权限注解支持

spring-mvc.xml进行如下配置:

 <!-- 开启Shiro的注解 使用这些注解就需要使用 AOP 的功能来进行判断,对类进行代理-->
    <aop:config proxy-target-class="true"></aop:config>
    
    <bean class="org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor">
        <property name="securityManager" ref="securityManager"/>
    </bean>

权限注解

@RequiresAuthentication

表示当前 Subject 已经通过 login 进行了身份验证;即 Subject.isAuthenticated() 返回 true。

@RequiresUser

表示当前 Subject 已经身份验证或者通过记住我登录的。

@RequiresGuest

表示当前 Subject 没有身份验证或通过记住我登录过,即是游客身份。

@RequiresRoles(value={“admin”, “user”}, logical= Logical.AND)

表示当前 Subject 需要角色 admin 和 user。

三、配置无权限异常信息统一处理方式

因springmvc重新定制异常处理方式所以配置的shiroFilter是无效的

<!-- 权限认证失败,则跳转到指定页面 -->
    <property name="unauthorizedUrl" value="/nopermission"/>

在spring-shiro.xml中配置 无权限异常信息统一处理方式:
<!--需要处理的特殊异常,用类名或完全路径名作key,异常页作为值-->
<!-- shiro权限异常处理 -->
<bean
	class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
	<property name="exceptionMappings">
		<props>
			<!-- 根据需要定义N多个错误异常转发 -->
			//<prop key="org.apache.shiro.authz.UnauthorizedException">/nopermission</prop>
			<prop key="org.apache.shiro.authz.UnauthenticatedException">redirect:/nopermission.jsp</prop>

		</props>
	</property>

Shiro Session管理

Shiro 提供了完整的企业级会话管理功能,不依赖于底层容器(如 web 容器 tomcat),不管 JavaSE 还是 JavaEE 环境都可以使用,提供了会话管理、会话事件监听、会话存储 / 持久化、容器无关的集群、失效 / 过期支持、对 Web 的透明支持、SSO 单点登录的支持等特性。即直接使用 Shiro 的会话管理可以直接替换如 Web 容器的会话管理。

spring-shiro.xml添加会话管理器

 <!-- session管理器 -->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
    <!--<bean id="sessionManager" class="com.zhunda.upms.client.shiro.UpmsSessionManager"> -->
        <!-- 设置全局会话超时时间,默认4小时(14400000) -->
        <property name="globalSessionTimeout" value="14400000"/>
        <!-- 是否在会话过期后会调用SessionDAO的delete方法删除会话 默认true -->
        <property name="deleteInvalidSessions" value="true"/>

        <!-- 会话验证器调度时间 -->
        <property name="sessionValidationInterval" value="1800000"/>
        <!-- session存储的实现 -->
        <property name="sessionDAO" ref="redisCacheSessionDAO"/>
        <property name="sessionIdCookieEnabled" value="true"/>
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
        <!-- 去除URL中的JSESSIONID -->
        <property name="sessionIdUrlRewritingEnabled" value="true" />
    </bean>
    
    <!-- 会话Cookie模板 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    	<!-- 多个项目共享JSESSIONID -->
        <property name="path" value="/" />
        <!-- 不会暴露给客户端 -->
        <property name="httpOnly" value="true"/>
        <!-- 设置Cookie的过期时间,秒为单位,默认-1表示关闭浏览器时过期Cookie -->
        <property name="maxAge" value="-1"/>
        <!-- Cookie名称 -->
        <property name="name" value="SSO-SESSIONID"/>
    </bean>


    <!--</bean>-->
    <!-- 会话Session ID生成器 -->
    <bean id="sessionIdGenerator" class="com.zhunda.upms.client.shiro.UuidSessionIdGenerator"/>

    <bean id="redisCacheSessionDAO" class="com.zhunda.upms.client.shiro.RedisCacheSessionDAO">
        <property name="sessionIdGenerator" ref="sessionIdGenerator"/>
    </bean>

安全管理器securityManager引用会话管理器

  <!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="upmsRealm"/>
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

Shiro RememberMe

什么是RememberMe?

Shiro提供了记住我(RememberMe)的功能,访问如一些网站时,关闭了浏览器下次再打开时还是能记住你是谁,下次访问时无需再登录即可访问,基本流程如下:

1.首先在登录页面选中 RememberMe 然后登录成功;如果是浏览器登录,一般会把 RememberMe 的 Cookie 写到客户端并保存下来;

2.关闭浏览器再重新打开;会发现浏览器还是记住你的;

3.访问一般的网页服务器端还是知道你是谁,且能正常访问; 但是当我们访问一些特殊网页,如查订单或进行支付时,此时还是需要再进行身份认证的,以确保当前用户还是你。

spring-shiro.xml配置rememberMe管理器

<!-- rememberMeManager管理器 -->
    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cookie" ref="rememberMeCookie" />
    </bean>

  <!-- 记住我cookie -->
    <bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="rememberMe" />
        <!-- 记住我cookie生效时间30-->
        <property name="maxAge" value="2592000" />
        <property name="httpOnly" value="true"/>
    </bean>
    
	<bean id="formAuthenticationFilter"

       class="org.apache.shiro.web.filter.authc.FormAuthenticationFilter">

       <!-- 表单中账号的input名称 -->

       <property name="usernameParam" value="usercode" />

       <!-- 表单中密码的input名称 -->

       <property name="passwordParam" value="password" />

       <property name="rememberMeParam" value="rememberMe"/>

       <!-- loginurl:用户登陆地址,此地址是可以http访问的url地址 -->

       <property name="loginUrl" value="/login" />

    </bean>

安全管理器securityManager引用rememberMeManager管理器

<!-- 安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <property name="sessionManager" ref="sessionManager" />
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 记住我 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
    </bean>

如果要自己做 RememeberMe,需要在登录之前这样创建 Token:UsernamePasswordToken(用户名,密码,是否记住我),如:

Subject subject = SecurityUtils.getSubject();
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
token.setRememberMe(true);
subject.login(token);

Shiro缓存机制

Shiro 内部相应的组件(DefaultSecurityManager)会自动检测相应的对象(如 Realm)是否实现了 CacheManagerAware 并自动注入相应的 CacheManager。

spring-shiro.xml添加缓存管理器

<!-- 缓存管理器 -->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
    <property name="cacheManagerConfigFile" value="classpath:ehcache.xml"></property>
    <property name="shared" value="true"></property>
    </bean>

添加缓存配置文件 ehcache.xml

<ehcache updateCheck="false" name="shiroCache">

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

securityManager安全管理器引用缓存管理器

  <!-- 配置安全管理器 -->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="userRealm" />
        <!--添加缓存机制-->
         <property name="cacheManager" ref="cacheManager" />
    </bean> 

清空缓存

1.用户正常/非正常退出,缓存会自动清空

2.若变更用户权限 , 且用户不退出系统 , 但由于用户权限信息已被缓存, 修改后的权限不会立即生效.

3.修改用户权限后,用户再次登录会自动调用realm从数据库查询权限,此时用户权限才是正确的.若修改权限后想立即清除缓存,则需要调用realm中定义的clearCache方法

4.在realm中定义clearCache方法

public void clearCache(){
	PrincipalCollection principals=SecurityUtils.getSubject().getPrincipals();
	super.clearCache(principals);
}

5.在用户角色or权限发生变化时调用clearCache()清除缓存

Shiro编码加密

1.添加凭证匹配器

在spring-shiro.xml中进行如下操作:

<!-- 凭证匹配器 -->
    <bean id="credentialsMatcher"
        class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <!--加密算法-->
        <property name="hashAlgorithmName" value="md5" />
        <!--散列次数-->
        <property name="hashIterations" value="2" />
    </bean>

2.自定义realm引用凭证匹配器

<!-- 配置自定义 realm -->
    <bean id="userRealm" class="cn.ybzy.shiro.security.ShiroRealm">
    <!--使用凭证匹配器-->
    <property name="credentialsMatcher" ref="credentialsMatcher" />
</bean>

3.修改自定义Realm

//认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        //从token中 获取用户身份信息
        String username = (String) token.getPrincipal();
        String password = new String((char[]) token.getCredentials());

        //假设模拟从数据库中查询出的密码是加密后的密文 : 明文(123456) + 盐(shiro) + 散列次数(2)
        String pwd ="b87dbd0bd4bcc6536a08d2027e329547";
        //返回认证信息由父类AuthenticatingRealm进行认证
        SimpleAuthenticationInfo simpleAuthenticationInfo = new SimpleAuthenticationInfo(
                username, pwd, ByteSource.Util.bytes("shiro"),getName());

        return simpleAuthenticationInfo;
    }

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/137147.html

(0)
飞熊的头像飞熊bm

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!