基于ssm的shiro搭建

4 篇文章 0 订阅
1 篇文章 0 订阅
  1. 配置web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:web="http://java.sun.com/xml/ns/javaee"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5">

  <!-- spring的配置文件-->
  <!--classpath:只会到你的class路径中查找找文件;
      classpath*:不仅包含class路径,还包括jar文件中(class路径)进行查找
-->
  <context-param>
    <param-name>contextConfigLocation</param-name>
    <!--<param-value>classpath*:spring-mybatis.xml,classpath*:spring-redis.xml</param-value>-->
    <param-value>classpath*:spring-mybatis.xml,classpath*:spring-redis.xml,classpath*:spring-shiro-web.xml</param-value>
  </context-param>
  <context-param>
    <param-name>log4jConfigLocation</param-name>
    <param-value>classpath*:log4j.properties</param-value>
  </context-param>
  <listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
  </listener>

  <!-- spring mvc核心:分发servlet -->
  <servlet>
    <servlet-name>mvc-dispatcher</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <!-- spring mvc的配置文件 -->
    <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>mvc-dispatcher</servlet-name>
    <url-pattern>/</url-pattern>
  </servlet-mapping>

  <!-- 中文过滤器 -->
  <filter>
    <filter-name>CharacterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <init-param>
      <param-name>encoding</param-name>
      <param-value>utf-8</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CharacterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>

  <!--*****跨域问题*****-->
  <filter>
    <filter-name>CORS</filter-name>
    <filter-class>com.wang.ssm.utils.MyFilters</filter-class>
    <init-param>
      <param-name>cors.allowOrigin</param-name>
      <param-value>*</param-value>
    </init-param>
    <init-param>
      <param-name>cors.supportedMethods</param-name>
      <param-value>GET, POST, HEAD, PUT, DELETE,OPTION</param-value>
    </init-param>
    <init-param>
      <param-name>cors.supportedHeaders</param-name>
      <param-value>Accept, Origin, X-Requested-With, Content-Type, Last-Modified</param-value>
    </init-param>
    <init-param>
      <param-name>cors.exposedHeaders</param-name>
      <param-value>Set-Cookie</param-value>
    </init-param>
    <init-param>
      <param-name>cors.supportsCredentials</param-name>
      <param-value>true</param-value>
    </init-param>
    <init-param>
      <param-name>cors.maxAge</param-name>
      <param-value>3600</param-value>
    </init-param>
  </filter>
  <filter-mapping>
    <filter-name>CORS</filter-name>
    <url-pattern>/*</url-pattern>
  </filter-mapping>


  <!-- 配置Shiro过滤器,先让Shiro过滤系统接收到的请求 -->
  <!-- 这里filter-name必须对应spring-shiro-web.xml中定义的<bean id="shiroFilter"/> -->
  <!-- 使用[/*]匹配所有请求,保证所有的可控请求都经过Shiro的过滤 -->
  <!-- 通常会将此filter-mapping放置到最前面(即其他filter-mapping前面),以保证它是过滤器链中第一个起作用的 -->
  <!-- shiro filter start -->
  <filter>
    <filter-name>shiroFilter</filter-name>
    <filter-class>
      org.springframework.web.filter.DelegatingFilterProxy
    </filter-class>
    <!-- 该值缺省为false,表示生命周期由SpringApplicationContext管理,设置为true则表示由ServletContainer管理 -->
    <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 filter end -->


</web-app>

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

    <!-- Shiro的web过滤器 -->
    <!-- id属性值要对应 web.xml中shiro的filter对应的bean -->
    <!-- Shiro主过滤器本身功能十分强大,其强大之处就在于它支持任何基于URL路径表达式的、自定义的过滤器的执行 -->
    <!-- Web应用中,Shiro可控制的Web请求必须经过Shiro主过滤器的拦截,Shiro对基于Spring的Web应用提供了完美的支持 -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- Shiro的核心安全接口,这个属性是必须的 -->
        <property name="securityManager" ref="securityManager"/>
        <property name="filters">
            <util:map>
                <entry key="authc" value-ref="shiroLoginFilter" />
            </util:map>
        </property>

        <!-- 未登录跳转页面,请求地址将由formAuthenticationFilter进行表单认证 -->
        <!-- 本项目通过ajax访问,由ShiroLoginFilter中处理返回json信息 -->
        <!--<property name="loginUrl" value="/user/login "/>-->

        <!-- 认证成功统一跳转到页面,建议不配置,shiro认证成功会默认跳转到上一个请求路径 -->
        <!-- 本项目通过ajax访问,loginController#loging中直接返回json信息 -->
        <!-- <property name="successUrl" value="/first.action"></property> -->

        <!-- 通过unauthorizedUrl指定没有权限操作时跳转页面,这个位置会拦截不到,下面有给出解决方法 -->
        <!-- 本项目通过ajax访问,由BaseController中@ExceptionHandler捕获异常处理 -->
        <!-- 注意:直接访问需要权限的页面(没有验证时)都会被定向到 loginUrl,而不会被unauthorizedUr拦截,只有内部跳转进入realm权限验证才会被此拦截-->
        <property name="unauthorizedUrl" value="/admin/unKnown"/>


        <!-- Shiro连接约束配置,即过滤链的定义 -->
        <!-- 此处可配合我的这篇文章来理解各个过滤连的作用http://blog.csdn.net/jadyer/article/details/12172839 -->
        <!-- 下面value值的第一个'/'代表的路径是相对于HttpServletRequest.getContextPath()的值来的 -->
        <!-- anon:它对应的过滤器里面是空的,什么都没做,这里.do和.jsp后面的*表示参数,比方说login.jsp?main这种 -->
        <!-- authc:该过滤器下的页面必须验证后才能访问,它是Shiro内置的一个拦截器org.apache.shiro.web.filter.authc.FormAuthenticationFilter -->

        <!-- 过滤器定义,从上到下执行,一般将/**放在最下面 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 对静态资源设置匿名访问 -->
                <!--开放登陆接口-->
                /admin/selectMenu = authc[admin:manage]
                <!--/login.html = anon-->
                <!-- /**=authc 所有的url都必须通过认证才可以访问 -->
                <!--/** = authc-->
                 /**=anon
                <!--所有的url都可以匿名访问,不能配置在最后一排,不然所有的请求都不会拦截 &ndash;&gt;-->
            </value>
        </property>
    </bean>

    <!--使用ajax访问,自定义未登录返回信息-->
    <bean id="shiroLoginFilter" class="com.wang.ssm.utils.shiro.ShiroLoginFilter"/>

    <!-- 会话管理器 -->
    <!--<bean id="sessionManager" class="com.wang.ssm.utils.shiro.MySessionManager">-->
        <!--&lt;!&ndash;其它相关设置&ndash;&gt;-->
    <!--</bean>-->
    <!--&lt;!&ndash; 会话ID生成器 &ndash;&gt;-->
    <!--<bean id="sessionIdGenerator" class="cn.suyan.shiro.session.UuIdSessionIdGenerator"/>-->

    <!--&lt;!&ndash; 会话Cookie模板 &ndash;&gt;-->
    <!--<bean id="sessionIdCookie" class="org.apache.shiro.web.servlet.SimpleCookie">-->
        <!--<constructor-arg value="sid"/>-->
        <!--<property name="httpOnly" value="true"/>-->
        <!--<property name="maxAge" value="-1"/>-->
    <!--</bean>-->

    <!--<bean id="rememberMeCookie" class="org.apache.shiro.web.servlet.SimpleCookie">-->
        <!--<constructor-arg value="rememberMe"/>-->
        <!--<property name="httpOnly" value="true"/>-->
        <!--<property name="maxAge" value="2592000"/>&lt;!&ndash; 30天 &ndash;&gt;-->
    <!--</bean>-->
    <!-- 解决shiro配置的没有权限访问时,unauthorizedUrl不跳转到指定路径的问题 -->
    <!--<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">-->
        <!--<property name="exceptionMappings">-->
            <!--<props>-->
                <!--<prop key="org.apache.shiro.authz.UnauthorizedException">/refuse</prop>-->
            <!--</props>-->
        <!--</property>-->
    <!--</bean>-->

    <!-- 继承自AuthorizingRealm的自定义Realm,即指定Shiro验证用户登录的类为自定义的ShiroDbRealm.java -->
    <!--
    	Shiro 从从Realm获取安全数据(如用户、角色、权限),就是说SecurityManager要验证用户身份,
    	那么它需要从Realm获取相应的用户进行比较以确定用户身份是否合法;
		也需要从Realm得到用户相应的角色/权限进行验证用户是否能进行操作;
		可以把Realm看成DataSource , 即安全数据源
	-->
    <bean id="myRealm" class="com.wang.ssm.utils.shiro.MyRealm">
        <!-- 将凭证匹配器设置到realm中,realm按照凭证匹配器的要求进行散列 -->
        <!--<property name="credentialsMatcher" ref="credentialsMatcher"/>-->
    </bean>

    <!-- Shiro默认会使用Servlet容器的Session,可通过sessionMode属性来指定使用Shiro原生Session -->
    <!-- 即<property name="sessionMode" value="native"/>,详细说明见官方文档 -->
    <!-- 这里主要是设置自定义的单Realm应用,若有多个Realm,可使用'realms'属性代替 -->
    <!--配置安全管理器-->
    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 注入realm -->
        <property name="realm" ref="myRealm"/>
        <!--记住我-->
        <!-- 记住密码管理 -->
        <property name="rememberMeManager" ref="rememberMeManager"/>
        <!-- 配置sessionManager,提供session管理 -->
        <property name="sessionManager" ref="sessionManager"/>

        <!--配置缓存-->
        <!--使用缓存可以避免需要授权信息时频繁的调用数据库查询的问题。
            原理很简单,只要在SecurityManager里注入CacheManager即可。
            我们可以自己定义CacheManager的实现,可以是ehcache、redis等等。
         -->
        <!--将缓存管理器,交给安全管理器-->
        <!--未知错误,待解决-->
        <!--<property name="cacheManager" ref="cacheManager"/>-->
    </bean>

    <!--2. 配置 CacheManager. 2.1需要加入 ehcache 的 jar 包及配置文件. 缓存管理-->
    <!--<bean id="cacheManager" class="com.wang.ssm.utils.shiro.ShiroSpringCacheManager">-->
        <!--<property name="cacheManager" ref="cacheManager"/>-->
    <!--</bean>-->

    <!-- shiro结合Session会话管理器 start -->
    <bean id="sessionManager" class="com.wang.ssm.utils.shiro.MySessionManager">
        <!-- session的失效时长,单位毫秒 1小时: 3600000, itzixi站点设置以 6小时 为主:21600000 -->
        <!-- 设置全局会话超时时间,默认30分钟,即如果30分钟内没有访问会话将过期 1800000 -->
        <property name="globalSessionTimeout" value="21600000"/>
        <!-- 删除失效的session -->
        <property name="deleteInvalidSessions" value="true"/>
        <!-- 是否开启会话验证器,默认是开启的 -->
        <property name="sessionValidationSchedulerEnabled" value="true"/>
    </bean>


    <!--rememberMe cookie-->
    <bean id="rememberMe" class="org.apache.shiro.web.servlet.SimpleCookie">
        <constructor-arg  value="rememberMe"/>
        <property name="httpOnly" value="true"/>
        <!--cookie 的最大失效时间 30天-->
        <property name="maxAge" value="259200"/>
    </bean>


    <!-- Remember me管理器 -->

    <bean id="rememberMeManager" class="org.apache.shiro.web.mgt.CookieRememberMeManager">
        <property name="cipherKey" value="#{T(org.apache.shiro.codec.Base64).decode('4AvVhmFLUs0KTA3Kprsdag==')}"/>
        <property name="cookie" ref="rememberMe"/>
    </bean>


    <!-- 保证实现了Shiro内部lifecycle函数的bean执行,可以自动的调用配置在spring ioc容器中 shiro bean 的生命周期方法 -->
    <bean id="lifecycleBeanPostProcessor" class="org.apache.shiro.spring.LifecycleBeanPostProcessor"/>

    <!-- 开启Shiro的注解(如@RequiresRoles,@RequiresPermissions),需借助SpringAOP扫描使用Shiro注解的类,并在必要时进行安全逻辑验证 -->
    <!--必须在配置了lifecycleBeanPostProcessor之后才可以使用-->
    <!-- 配置以下两个bean即可实现此功能 -->
    <!-- Enable Shiro Annotations for Spring-configured beans. Only run after the lifecycleBeanProcessor has run -->
    <!-- 由于本例中并未使用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>


    <!-- 凭证匹配器 -->
    <!--<bean id="credentialsMatcher" class="org.apache.shiro.authc.credential.HashedCredentialsMatcher">-->
        <!--&lt;!&ndash; 加密算法 &ndash;&gt;-->
        <!--<property name="hashAlgorithmName" value="md5"/>-->
        <!--&lt;!&ndash; 迭代次数 &ndash;&gt;-->
        <!--<property name="hashIterations" value="1"/>-->
    <!--</bean>-->


</beans>

  1. 自定义realm
package com.wang.ssm.utils.shiro;

import com.auth0.jwt.internal.org.apache.commons.codec.digest.DigestUtils;
import com.auth0.jwt.internal.org.apache.commons.lang3.StringUtils;
import com.auth0.jwt.internal.org.apache.commons.lang3.builder.ReflectionToStringBuilder;
import com.auth0.jwt.internal.org.apache.commons.lang3.builder.ToStringStyle;
import com.wang.ssm.entity.Role;
import com.wang.ssm.entity.User;
import com.wang.ssm.service.AdminService;
import com.wang.ssm.service.UserService;
import com.wang.ssm.utils.CacheInspect;
import com.wang.ssm.utils.CacheUtil;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.session.Session;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.springframework.beans.factory.annotation.Autowired;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 自定义的Realm
 */
public class MyRealm extends AuthorizingRealm {
    @Autowired
    private UserService userService;

    @Autowired
    private AdminService adminService;

    @Autowired
    private CacheUtil cacheUtil;

    // 设置realm的名称
    @Override
    public void setName(String name) {
        super.setName("customRealm");
    }

    /**
     * 认证的方法,登录时执行
     *
     *      * 验证当前登录的Subject
     *      * 经测试:本例中该方法的调用时机为LoginController.login()方法中执行Subject.login()的时候
     *
     *
     * @param token
     * @return
     * @throws AuthenticationException
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        //System.out.println("————身份认证方法————");
        // token是用户输入的用户名和密码
        // 第一步从token中取出用户名

        //获取基于用户名和密码的令牌
        //实际上这个authcToken是从LoginController里面currentUser.login(token)传过来的
        //两个token的引用都是一样的,本例中是org.apache.shiro.authc.UsernamePasswordToken@33799a1e
        UsernamePasswordToken authcToken = (UsernamePasswordToken)token;
        System.out.println("验证当前Subject时获取到token为" + ReflectionToStringBuilder.toString(authcToken, ToStringStyle.MULTI_LINE_STYLE));

        final String username = (String) token.getPrincipal();
        String password = null;
        final Object credentials = token.getCredentials();
        if (credentials instanceof char[]) {
            password = new String((char[]) credentials);
        }

        User user = new User();
        user.setUserName(username);
        user.setPassword(password);
        String userToken = null;
        // 从数据库查询到密码
        try {
           userToken = userService.login(user);
        } catch (Exception e) {
            e.printStackTrace();
        }

        //配合shiro配置的md5加密(应该可以配置为不加密)
//        if (null !=user.getPassword()) {
//            user.setPassword(DigestUtils.md5Hex(user.getPassword()));
//        }

        //加密的盐
        //String salt = user.getSalt();

        final HashMap<String, Object> principal = new HashMap<>();
        principal.put("token",userToken);
        principal.put("user",user);

        return new SimpleAuthenticationInfo(principal, user.getPassword(), this.getName());
    }

    /**
     * 授权的方法,每次访问需要权限的接口都会执行
     *为当前登录的Subject授予角色和权限
     *      * -----------------------------------------------------------------------------------------------
     *      * 经测试:本例中该方法的调用时机为需授权资源被访问时
     *      * 经测试:并且每次访问需授权资源时都会执行该方法中的逻辑,这表明本例中默认并未启用AuthorizationCache
     *      * 个人感觉若使用了Spring3.1开始提供的ConcurrentMapCache支持,则可灵活决定是否启用AuthorizationCache
     *      * 比如说这里从数据库获取权限信息时,先去访问Spring3.1提供的缓存,而不使用Shior提供的AuthorizationCache
     * @param principals
     * @return
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        //System.out.println("————权限认证————");
        //从principals获取主身份信息
        //将getPrimaryPrincipal方法返回值转为真实身份类型(在上边doGetAuthenticationInfo认证通过填充到SimpleAuthenticationInfo中的身份类型)

        //以下方法等效SecurityUtils.getSubject().getPrincipal() principals.getPrimaryPrincipal()
        //Map principal = (Map) SecurityUtils.getSubject().getPrincipal();

        //获取当前的用户
        // loginService.buildSessionAttr方法生成了包含 List<String> permissions,key为"permissions";


        Map principal = (Map) principals.getPrimaryPrincipal();
        User user = (User) principal.get("user");
        Map<String, List<Role>> permissions = adminService.getPermissions(user);

        //查到权限数据,返回授权信息(要包括上边的permissions)
        SimpleAuthorizationInfo simpleAuthorizationInfo = new SimpleAuthorizationInfo();
//        simpleAuthorizationInfo.addStringPermissions(permissions);//这里添加用户有的权限列表
//        simpleAuthorizationInfo.addRole(user.getRoleId());//这里添加用户所拥有的角色
        simpleAuthorizationInfo.addRole("admin");
        simpleAuthorizationInfo.addStringPermission("admin:ma");
        return simpleAuthorizationInfo;
    }

    /**
     * 将一些数据放到ShiroSession中,以便于其它地方使用
     * 比如Controller里面,使用时直接用HttpSession.getAttribute(key)就可以取到
     */
    private void setAuthenticationSession(Object value){
        Subject currentUser = SecurityUtils.getSubject();
        if(null != currentUser){
            Session session = currentUser.getSession();
            System.out.println("当前Session超时时间为[" + session.getTimeout() + "]毫秒");
            session.setTimeout(1000 * 60 * 60 * 2);
            System.out.println("修改Session超时时间为[" + session.getTimeout() + "]毫秒");
            session.setAttribute("currentUser", value);
        }
    }

}

  1. 自定义过滤器shiroLoginFilter
package com.wang.ssm.utils.shiro;

import com.alibaba.fastjson.JSON;
import com.wang.ssm.entity.User;
import com.wang.ssm.utils.CacheUtil;
import com.wang.ssm.utils.JWT;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.authc.FormAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.util.HashMap;
import java.util.Map;

//继承的实际就是authc代表的过滤器,可以查阅源码重写父类的方法实现需求
public class ShiroLoginFilter extends FormAuthenticationFilter {
    private static final Logger log = LoggerFactory.getLogger(ShiroLoginFilter.class);

    @Autowired
    private CacheUtil cacheUtil;

    /**
     * 如果isAccessAllowed返回false 则执行onAccessDenied
     *顾名思义,这是判断时候允许通过的方法,在这个方法里处理了options请求,
            *然后接下来直接调用父类的方法实现着个方法真正需要做的事-----总结起来就是我们重写filter时
     * ,如果你能力够那么你彻底覆写也没问题,
            *相反,你可以先添加代码实现自己的需求,
            *再调用父类同名的方法实现这个方法在shiro真正做到的功能
     *
     * @param request
     * @param response
     * @param mappedValue
     * @return
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        boolean isAllowed = false;
        //前端(某些框架)测试接口(OPTIONS)直接放行
        if (request instanceof HttpServletRequest) {
            if (((HttpServletRequest) request).getMethod().toUpperCase().equals("OPTIONS")) {
               return  true;
            }
        }
        Subject subject = getSubject(request, response);
//        Object principal = subject.getPrincipal();


        //使用JWT的token验证,验证是否登录
//        response.setCharacterEncoding("utf-8");
//        HttpServletRequest request1 = (HttpServletRequest) request;
//        String authorization = request1.getHeader("Authorization");
        Map<String,Object> principal = (Map<String, Object>) subject.getPrincipal();
        String token = (String) principal.get("token");
        Object unSign = JWT.unsign(token, String.class);
        if(null != unSign){
            String[] split = ((String) unSign).split(",");
            Object o = cacheUtil.get(split[0]);

            //创建字节数组和序列化输入流,将字节数据读取到字节输入流
            ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(((byte[]) o));
            ObjectInputStream objectInputStream = null;
            try {
                //将字节输入流转化为序列化输入流
                objectInputStream = new ObjectInputStream(byteArrayInputStream);

                //序列化成为对象
                User user = (User) objectInputStream.readObject();
            } catch (Exception e) {
                throw  new RuntimeException("缓存读取失败");
            }
        }

        boolean permitted = subject.isPermitted("admin:ma");

        return unSign!= null;
    }

    /**
     *
     * 请求未通过,肯定是没有通过shiro的认证
     * 重写这个方法防止跳转到Login
     * 自己看源码
     *
     * @param request
     * @param response
     * @return true-继续往下执行,false-该filter过滤器已经处理,不继续执行其他过滤器
     * @throws Exception
     */
    @SuppressWarnings("unchecked")
    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        log.info("用户未登录");
        final HttpServletRequest request2 = (HttpServletRequest) request;
        final HttpServletResponse response2 = (HttpServletResponse) response;
        //ajax访问接口返回数据结构
        if (isAjaxRequest(request2)) {// ajax接口
            //这里是个坑,如果不设置的接受的访问源,那么前端都会报跨域错误,因为这里还没到corsConfig里面
            response2.setHeader("Access-Control-Allow-Origin", request2.getHeader("Origin"));
            response2.setHeader("Access-Control-Allow-Credentials", "true");
            response2.setCharacterEncoding("UTF-8");
            response2.setContentType("application/json");

            Map responseData = new HashMap();
            responseData.put("state", "unauthorized");
            responseData.put("code", 401);
            responseData.put("msg", "用户未登录");

            String result = JSON.toJSONString(responseData);
            PrintWriter out;
            try {
                out = response2.getWriter();
                out.print(result);
                out.flush();
            } catch (IOException e) {
                log.error("返回数据失败!", e);
            }
        } else {
            //其他情况
            //shiro处理
//            super.onAccessDenied(request, response);

            //其他处理方式

            // 页面,直接跳转登录页面
            //redirect("login.html", request2, response2);

            //web.xml处理
            response2.setCharacterEncoding("UTF-8");
            response2.setContentType("application/json");

            Map responseData = new HashMap();
            responseData.put("state", "unauthorized");
            responseData.put("code", 1);
            responseData.put("msg", "身份验证失败");

            String result = JSON.toJSONString(responseData);
            PrintWriter out;
            try {
                out = response2.getWriter();
                out.print(result);
                out.flush();
            } catch (IOException e) {
                log.error("返回数据失败!", e);
            }
//            response2.setContentType("application/json");
//            response2.setStatus(401);// 客户试图未经授权访问受密码保护的页面。
        }
        return false;
    }

    /**
     * isAjaxRequest:判断请求是否为Ajax请求. <br/>
     *
     * @author chenzhou
     * @param request 请求对象
     * @return boolean
     * @since JDK 1.6
     */
    public boolean isAjaxRequest(HttpServletRequest request){
        String header = request.getHeader("x-requested-with, content-type,Authorization");
        return "XMLHttpRequest".equals(header);
    }
}

  1. 自定义session获取
package com.wang.ssm.utils.shiro;

import org.apache.commons.lang.StringUtils;
import org.apache.shiro.web.servlet.ShiroHttpServletRequest;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.io.Serializable;

/**
 * 自定义sessionId的获取
 */
public class MySessionManager extends DefaultWebSessionManager {
    private static final String AUTHORIZATION = "Authorization";

    private static final String REFERENCED_SESSION_ID_SOURCE = "cookie";

    public MySessionManager() {
        super();
    }

    @Override
    protected Serializable getSessionId(ServletRequest request, ServletResponse response) {
        String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION);
        //如果请求头中有 Authorization 则其值为sessionId
        if (!StringUtils.isBlank(token)) {
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_SOURCE, REFERENCED_SESSION_ID_SOURCE);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID, token);
            request.setAttribute(ShiroHttpServletRequest.REFERENCED_SESSION_ID_IS_VALID, Boolean.TRUE);
            return token;
        } else {
            //否则按默认规则从cookie取sessionId
            return super.getSessionId(request, response);
        }
    }
}

  1. 测试:usercontroller
    session.getId()获取到的是当前的subject的session值,传递到前台之后,前台的请求头Authorization带上这个值
/**
     * 登录
     */
    @PostMapping("/login")
    public R login(@RequestBody User user) {
        // 在认证提交前准备 token(令牌)
        UsernamePasswordToken token = new UsernamePasswordToken(user.getUserName(), user.getPassword());
        try {
        // 从SecurityUtils里边创建一个 subject
            Subject subject = SecurityUtils.getSubject();
            // 执行认证登陆
            subject.login(token);
            //Shiro认证通过后会将user信息放到subject内,生成token并返回
            Session session = subject.getSession();
            Serializable id = session.getId();
//            subject.isAuthenticated();
            return R.ok(id);
        } catch (Exception e) {
            return R.failed(e.getMessage());
        }
    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值