当前位置: 首页>后端>正文

8.shiro整合SSM

1.整合SSM并且实现用户登陆和菜单权限

具体整合参考https://www.jianshu.com/p/d0ffe2505216

2.将shiro整合到ssm中
a)添加shiro的相关jar

<!-- shiro -->
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>1.4.0</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-spring</artifactId>
      <version>1.4.0</version>
    </dependency>

b)在web.xml中添加shiro配置

<!--  配置shiro的过滤器,通过代理配置,对象由spring容器来创建,但是交由servlet容器来管理-->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
    <!-- 设置true由servlet容器控制filter的生命周期 -->
    <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>
  <!-- shiro 过滤器 end -->

c)springmvc.xml

    <!--设置开启shiro的注解,例如@RequiresRoles("admin")-->
    <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>
    <!--配置异常处理,处理shiro的权限和认证异常跳转-->
    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <property name="exceptionMappings">
            <props>
                <!--授权异常-->
                <prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop>
                <!--认证异常-->
                <prop key="org.apache.shiro.authz.UnauthenticatedException">/toLogin</prop>
            </props>
        </property>
    </bean>

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.xsd">

    <!--注入自定义的Realm-->
    <bean id="customRealm" class="com.lyh.realm.customRealm">
        <property name="credentialsMatcher" ref="credentialsMatcher"/>
    </bean>
    <!--配置凭证匹配器,加凭证匹配器后要对实体类序列化-->
    <bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">
        <property name="hashAlgorithmName" value="md5"/>
        <property name="hashIterations" value="2"/>
    </bean>
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm"/>
    </bean>

    <!-- 配置 Bean 后置处理器: 会自动的调用和 Spring 整合后各个组件的生命周期方法. -->
    <bean id="lifecycleBeanPostProcessor"
          class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!--配置ShiroFilter-->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <property name="securityManager" ref="securityManager"/>
        <!--登入页面,当访问需要认证的资源时,如果没有认证将自动跳转到该url,不配置默认到根目录下的login.jsp-->
        <property name="loginUrl" value="/"/>
        <!--登入成功页面,配置认证成功后跳转的url,通常不设置,因为如果不设置认证成功后跳转上一个URL-->
        <!--  <property name="successUrl" value="/index.jsp"/>-->
        <!--  配置用户没有访问权限时跳转的页面-->
        <property name="unauthorizedUrl" value="/refuse"/>

        <!--URL的拦截,配置shiro的过滤器链-->
        <property name="filterChainDefinitions" >
            <value>
                /=anon
                /toLogin=anon
                /logout = logout   <!--logout默认跳转到根目录下,可以重新指定-->
                /refuse=anon
                /**=authc
            </value>
        </property>
    </bean>
    <!--重新定义logout过滤器让其使用自己指定的跳转url,id不能改变只能是logout-->
    <bean id="logout" class="org.apache.shiro.web.filter.authc.LogoutFilter">
        <property name="redirectUrl" value="/refuse"/>
    </bean>
</beans>
Shiro中默认的过滤器
8.shiro整合SSM,第1张

d)写好自定义realm

import com.lyh.domain.User;
import com.lyh.mapper.UserMapper;
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.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 org.springframework.beans.factory.annotation.Autowired;

public class customRealm extends AuthorizingRealm {
    @Autowired
    UserMapper userMapper;

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

    //授权
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        String username = principalCollection.getPrimaryPrincipal().toString();
        User userByName = userMapper.getUserByName(username);
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        info.addRole(userByName.getRoles());
        info.addStringPermission("user:add");
        return info;
    }

    //认证
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        String username = authenticationToken.getPrincipal().toString();
        User userByName = userMapper.getUserByName(username);
        ByteSource pwd_salt = ByteSource.Util.bytes(userByName.getPassword_salt());

        return new SimpleAuthenticationInfo(username,userByName.getPassword(),pwd_salt,getName());
    }
}

controller代码:

import com.lyh.mapper.UserMapper;
import com.lyh.domain.User;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.annotation.RequiresRoles;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpSession;

@Controller
public class UserController {
    @Autowired
    UserMapper userMapper;

    @RequestMapping("/")
    public ModelAndView hostPage(){
        ModelAndView modelAndView = new ModelAndView("login");

        return modelAndView;
    }

    @RequestMapping("/getUser/{id}")
    @RequiresRoles("admin")
    public ModelAndView getUser(@PathVariable Integer id){
        ModelAndView modelAndView = new ModelAndView("index");
        User userById = userMapper.getUserById(id);
        modelAndView.addObject("user",userById);
        return modelAndView;
    }

    @RequestMapping("/toLogin")
    public ModelAndView login(String userName, String passwd, HttpSession session) {
        ModelAndView modelAndView = new ModelAndView();
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(userName, passwd);
        try {
            subject.login(token);
        } catch (Exception e) {
            modelAndView.addObject("msg", "用户名或密码错误!");
            modelAndView.setViewName("login");
            return modelAndView;
        }
        User userByName = userMapper.getUserByName(userName);
        session.setAttribute("login_user",userByName);
        modelAndView.setViewName("index");
        return modelAndView;
    }

    @RequestMapping("/refuse")
    public ModelAndView refuse() {
        ModelAndView modelAndView = new ModelAndView("refuse");

        return modelAndView;
    }
}
注解名称 解释
@RequiresAuthentication 表示当前Subject已经通过login身份验证;即Subject.isAuthenticated() == true;否则就拦截
@RequiresUser 表示当前Subject已经通过login身份验证或通过记住我登录;否则就拦截
@RequiresGuest 表示当前Subject没有身份验证或通过记住我登录过,即是游客身份
@RequiresRoles(value ={"admin", "user"}, logical=Logical.AND) 表示当前Subject需要同时(由Logical.AND体现)拥有admin和user角色;否则拦截
@RequiresPermissions(vale={"user:a","user:b"}, logical=Logical.OR) 表示当前Subject需要拥有user:a或者(由Logical.OR体现)user:b角色;否则拦截

JSP页面授权

Shiro提供了JSTL标签用于在JSP/GSP页面进行权限控制;首先需要导入标签库:

<%@ taglib prefix="shiro" uri="http://shiro.apache.org/tags" %>
标签名称 作用
<shiro:guest> 用户没有身份验证时显示相应的信息,即游客访问信息
<shiro:user> 用户已经身份验证、记住我登录后显示相应的信息,未登录用户将会拦截
<shiro:authenticated> 用户已经身份验证通过,即Subject.isAuthenticated() == true;未登录或记住我登录的都会拦截
<shiro:notAuthenticated> 用户已经身份验证通过,但是Subject.isAuthenticated() == false,即可能是通过记住我登录的
<shiro:principal> 显示用户身份信息,默认调用Subject.getPrincipal()获取用户登录信息
<shiro:hasRole> 如:<shiro:hasRole name="admin">,如果当前Subject有admin角色就显示数据,类似于@RequiresRoles()注解;否则就拦截
<shiro:hasAnyRole> 如:<shiro:hasAnyRole name="admin,user">,如果当前Subject有admin或user角色就显示数据,类似于@RequireRoles(Logical=Logical.OR)注解;否则将就拦截
<shiro:lackRole> 如果当前Subject没有角色就显示数据
<shiro:hasPermission> 如:<shiro:hasPermission name="user:create">,如果当前Subject有user:create权限,就显示数据;否则就拦截
<shiro:lacksPermission> 如:<shiro:lacksPermission name="user:create">,如果当前Subject没有user:create权限,就显示数据;否则拦截

3.缓存
每次权限检查都会到数据库中获取权限,这样效率很低,可以通过设置缓存来解决该问题。shiro可以和ehcache或者redis集成。在这里使用ehcache来缓存数据。

a)导入ehcache相关jar包。shiro默认集成了一个ehcache配置文件,也可以自己添加一个配置文件
pom文件:

    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-ehcache</artifactId>
      <version>1.4.0</version>
    </dependency>
    <!-- ehcache  -->
    <dependency>
      <groupId>net.sf.ehcache</groupId>
      <artifactId>ehcache-core</artifactId>
      <version>2.6.11</version>
    </dependency>

b)Shiro也集成了缓存机制,例如Shiro提供了CachingRealm,提供了一些基础的缓存实现。首先我们要开启Shiro的缓存管理,在XML中进行如下配置:

    <!--配置ehcache,指定ehcache配置文件路径,不指定就用默认的配置-->
    <bean id="cacheManager" class="org.apache.shiro.cache.ehcache.EhCacheManager">
        <property name="cacheManagerConfigFile" value="classpath:ehcache/ehcache.xml"/>
    </bean>

按照路径配置ehcache文件ehcache/ehcache.xml:

<?xml version="1.0" encoding="UTF-8"?>
<ehcache >
        <diskStore path="java.io.tmpdir"/>
        <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
            />
</ehcache>

设置SecurityManager的cacheManager:

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm"/>
        <!--把上面注入好的cacheManager设置进securityManager-->
        <property name="cacheManager" ref="cacheManager"/>
    </bean>

在自定义的Realm实现中配置缓存的实现(也可以不配置已经默认开启缓存):

    <!--注入自定义的Realm-->
    <bean id="customRealm" class="com.lyh.realm.customRealm">
        <!-- 使用credentialsMatcher实现密码验证服务 -->
        <property name="credentialsMatcher" ref="credentialsMatcher"/>

        <!-- 是否启用缓存 -->
        <property name="cachingEnabled" value="true"/>
        <!-- 是否启用身份验证缓存 -->
        <property name="authenticationCachingEnabled" value="true"/>
        <!-- 缓存AuthenticationInfo信息的缓存名称 -->
        <property name="authenticationCacheName" value="authenticationCache"/>
        <!-- 是否启用授权缓存,缓存AuthorizationInfo信息 -->
        <property name="authorizationCachingEnabled" value="true"/>
        <!-- 缓存AuthorizationInfo信息的缓存名称 -->
        <property name="authorizationCacheName" value="authorizationCache"/>
    </bean>

c)如果在运行过程中,主体的权限发生变化,那么应该从spring容器中调用realm中的清理缓存方法。

    //清理缓存
    public void clearCache() {
        Subject subject = SecurityUtils.getSubject();
        PrincipalCollection principals = subject.getPrincipals();
        super.clearCache(principals);
    }

处理修改权限的业务代码后调用上述方法

userMapper.addPermission(permission_username);
customRealm.clearCache();

4.会话管理

    <!--配置会话管理器sessionManager-->
    <bean id="sessionManager" class="org.apache.shiro.web.session.mgt.DefaultWebSessionManager">
        <!-- 设置全局会话过期时间:默认30分钟 -->
        <property name="globalSessionTimeout" value="1800000"/>
        <!-- 删除无效session -->
        <property name="deleteInvalidSessions" value="true"/>
        <!-- 是否启用sessionIdCookie,默认是启用的 -->
        <property name="sessionIdCookieEnabled" value="true"/>
        <!-- 会话Cookie -->
        <property name="sessionIdCookie" ref="sessionIdCookie"/>
    </bean>
    <!-- 会话Cookie模板 -->
    <bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg value="sid"/>
        <!-- 如果设置为true,则客户端不会暴露给服务端脚本代码,有助于减少某些类型的跨站脚本攻击 -->
        <property name="httpOnly" value="true"/>
        <property name="maxAge" value="-1"/><!-- maxAge=-1表示浏览器关闭时失效此Cookie -->
    </bean>

还要将sessionManager注入到SecurityManager中:

    <!--配置securityManager,设置自定义realm,缓存,会话-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="realm" ref="customRealm"/>
        <!--把上面注入好的cacheManager设置进securityManager-->
        <property name="cacheManager" ref="cacheManager"/>
        <!-- 注入sessionManager -->
        <property name="sessionManager" ref="sessionManager"/>
    </bean>

5.实现Remember功能

在Shiro会话管理时我们就讲到会话的功能,例如:Shiro实现了RememberMe记住我的功能,当用户在登录页面中勾选了记住我,再浏览器关闭后再次访问系统发现是可以直接登录的;但是如果没有实现这一功能,Shiro默认设置浏览器关闭后立即清除缓存,那么再次打开浏览器要重新进行登录。

拓展

RememberMe和使用Subject.login(token)登录是有所不同的,RememberMe是使用缓存Cookie的技术实现的登录,在前面讲到的一些权限注解中就说到了两者的区别。

RememberMe的配置实现

在配置文件中写入:

<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">
    <constructor-arg value="rememberMe"/>
    <property name="httpOnly" value="true"/>
    <property name="maxAge" value="2592000"/><!-- 30天 -->
</bean>

<!-- rememberMe管理器 -->
<bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
    <!-- cipherKey是加密rememberMe Cookie的密匙,默认AES算法 -->
    <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
    <property name="cookie" ref="rememberMeCookie"/>
</bean>

SecurityManager中设置rememberMeManager:

<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <property name="rememberMeManager" ref="rememberMeManager"/>
</bean>

在登录表单中添加一个checkbox:

<input type="checkbox" name="remember">请记住我

如果用户勾选了这个复选框,点击登录按钮提交后台的参数中会多一个remember参数,且值是on(如果用户没有勾选,提交表单中就不存在这个参数);所以我们修改Controller的登录方法:

    @RequestMapping("doLogin")
    public ModelAndView login(String username, String pwd, String remember, HttpSession session){
        ModelAndView modelAndView = new ModelAndView();
        Subject subject = SecurityUtils.getSubject();
        UsernamePasswordToken token = new UsernamePasswordToken(username, pwd);

        if (remember != null){
            if (remember.equals("on")) {
                //说明选择了记住我
                token.setRememberMe(true);
            } else {
                token.setRememberMe(false);
            }
        }else{
            token.setRememberMe(false);
        }

        try {
            subject.login(token);
        } catch (Exception e) {
            modelAndView.addObject("msg", "用户名或密码错误!");
            modelAndView.setViewName("login");
            return modelAndView;
        }
        User userByName = userMapper.selectByUsername(username);
        session.setAttribute("login_user",userByName);
        modelAndView.setViewName("user");
        return modelAndView;
    }

把filterChainDefinitions中的拦截器从authc改成user拦截器区别见上图

        <!--URL的拦截,配置shiro的过滤器链-->
        <property name="filterChainDefinitions" >
            <value>
                /goLogin=anon
                /doLogin=anon
                /logout = logout   <!--logout默认跳转到根目录下,可以重新指定-->
                /refuse=anon
                /**=user
            </value>
        </property>

注意:

走shiro的记住我方式登录后发现session数据丢失,解决办法我们自己写一个拦截器,验证当通过记住我方式登录后,把session加进去

实现HandlerInterceptor中的preHandle()方法
截取登录时的subject,判断是否是通过rememberMe登录,将principal手动添加到session中

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.lyh.domain.User;
import com.lyh.mapper.UserMapper;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;



/**
 * 拦截rememberMe的请求,添加user到session中
 * @author echo
 *
 */
public class RememberMeInterceptor implements HandlerInterceptor {
    @Autowired
    UserMapper userMapper;

    public RememberMeInterceptor() {
        // TODO Auto-generated constructor stub
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("测试方法进入preHandle");
        // 获取session中的subject
        Subject subject = SecurityUtils.getSubject();

        // 判断是不是通过记住我登录
        if( !subject.isAuthenticated() && subject.isRemembered()) {
            Session session = subject.getSession();
            User user= (User)subject.getPrincipal();

            User userByName = userMapper.getUserByName(user.getUsername());
            session.setAttribute("login_user",userByName);
        }
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        // TODO Auto-generated method stub

    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        // TODO Auto-generated method stub

    }
}

在springmvc.xml文件配置拦截器:

    <mvc:interceptors>
        <!--如果配置了多个拦截器,则按顺序执行 -->
        <mvc:interceptor>
            <!-- /**表示所有url包括子url路径 -->
            <mvc:mapping path="/**"/>
            <mvc:exclude-mapping path="/goLogin"/>
            <mvc:exclude-mapping path="/toLogin"/>
            <mvc:exclude-mapping path="/logout"/>
            <bean class="com.lyh.utils.RememberMeInterceptor"/>

        </mvc:interceptor>
    </mvc:interceptors>

注意jsp标签和拦截器<shiro:user>与<shiro:authenticated>和user与authc的区别不要用错。


https://www.xamrdz.com/backend/3m61935788.html

相关文章: