Shiro禁用Session,使用SSM+JWT+Shiro進行無狀態RESTful API

應用場景:html

因爲項目後端管理系統不僅是一個服務,並且不僅在Web端運行,還會在移動端App使用,想要使用JWT方式進行無狀態的RESTful API交互,也就是登陸後生成token並返回給前端,前端每次請求時都在請求頭裏面添加token,後端驗證有效性。因此Shiro自帶的Session要禁用掉,同時要從新寫JWT的過濾器。前端

 

使用shiro+ssm+jwt的一些前期準備:java

禁用session
JWT實現工具,這個網上能夠找到不少,這裏就不貼代碼了
自定義realm,繼承AuthorizingRealm,重寫認證和受權兩個方法
自定義filter,繼承AccessControlFilter,重寫方法isAccessAllowed,onAccessDeniedweb

 

1、首先,禁用shiro的session,先添加shiro的依賴到pomspring

<!--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-web</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version>
</dependency>
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-ehcache</artifactId>
    <version>1.4.0</version>
</dependency>

 

 

2、禁用 session數據庫

package com.tg.higo.shiro;

import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.SubjectContext;
import org.apache.shiro.web.mgt.DefaultWebSubjectFactory;

public class JwtDefaultSubjectFactory extends DefaultWebSubjectFactory {

    @Override
    public Subject createSubject(SubjectContext context) {
        // 不建立 session
        context.setSessionCreationEnabled(false);
        return super.createSubject(context);
    }
}

 

配置  applicationContext-shiro.xml
<bean id="subjectFactory" class="com.tg.higo.shiro.JwtDefaultSubjectFactory"></bean>


  <!-- 安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 配置 realm -->
        <property name="realm" ref="bosRealm"/>
        <property name="subjectFactory" ref="subjectFactory"/>

    </bean>

  3、 重寫filter 過濾器apache

 

package com.tg.higo.shiro;

import com.tg.higo.common.utils.StringUtil;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.filter.authc.BasicHttpAuthenticationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;

public class JwtFilter extends AccessControlFilter {

    /**
     * 日誌對象
     */
    protected Logger logger = LoggerFactory.getLogger(AdminShiroRealm.class);


    /*
     * 1. 返回true,shiro就直接容許訪問url
     * 2. 返回false,shiro纔會根據onAccessDenied的方法的返回值決定是否容許訪問url
     * */
    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
        logger.warn("isAccessAllowed 方法被調用");
        //這裏先讓它始終返回false來使用onAccessDenied()方法
        return false;
    }

    /**
     * 返回結果爲true代表登陸經過
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        logger.warn("onAccessDenied 方法被調用");
        //這個地方和前端約定,要求前端將jwtToken放在請求的Header部分

        //因此之後發起請求的時候就須要在Header中放一個Authorization,值就是對應的Token
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = getTokenFromRequest(request);
        if(StringUtil.isBlank(jwt)){
            onLoginFail(servletResponse);
            //調用下面的方法向客戶端返回錯誤信息
            return false;
        }
//        String jwt = request.getHeader("Authorization");
        logger.info("請求的 Header 中藏有 jwtToken {}", jwt);
        JwtToken jwtToken = new JwtToken(jwt);
        /*
         * 下面就是固定寫法
         * */
        try {

            // 委託 realm 進行登陸認證
            //因此這個地方最終仍是調用JwtRealm進行的認證
            getSubject(servletRequest, servletResponse).login(jwtToken);
            //也就是subject.login(token)
        } catch (Exception e) {
//            e.printStackTrace();
            logger.info(e.getMessage());
            onLoginFail(servletResponse);
            //調用下面的方法向客戶端返回錯誤信息
            return false;
        }

        return true;
        //執行方法中沒有拋出異常就表示登陸成功
    }

    // 從請求中獲取 token
    private String getTokenFromRequest(ServletRequest request) {
        HttpServletRequest req = (HttpServletRequest) request;
        return req.getHeader("Token");
    }

    //登陸失敗時默認返回 401 狀態碼
    private void onLoginFail(ServletResponse response) throws IOException {

        HttpServletResponse httpResponse = (HttpServletResponse) response;
        httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        httpResponse.getWriter().write("login error");


    }


}

 

4、 重寫Realm  ,定義認證,受權後端

package com.tg.higo.shiro;

import com.tg.higo.common.utils.JwtUtils;
import com.tg.higo.dao.UserDtoMapper;
import com.tg.higo.exception.RRException;
import com.tg.higo.model.UserDto;
import com.tg.higo.model.dto.User;
import org.apache.commons.lang.StringUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.authz.AuthorizationException;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.annotation.Resource;
import java.util.HashSet;
import java.util.Set;

public class AdminShiroRealm  extends AuthorizingRealm {




    /**
     * 日誌對象
     */
    protected Logger logger = LoggerFactory.getLogger(AdminShiroRealm.class);


    /*
     * 多重寫一個support
     * 標識這個Realm是專門用來驗證JwtToken
     * 不負責驗證其餘的token(UsernamePasswordToken)
     * */
    @Override
    public boolean supports(AuthenticationToken token) {
        //這個token就是從過濾器中傳入的jwtToken
        return token instanceof JwtToken;
    }



    //認證
    //這個token就是從過濾器中傳入的jwtToken
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {

        String jwt = (String) token.getCredentials();
//        if (jwt == null) {
//
//            throw new NullPointerException("jwtToken 不容許爲空");
//        }
        //判斷
//        JwtUtils jwtUtil = new JwtUtils();
        if (!JwtUtils.validToken(jwt)) {
            throw new UnknownAccountException();
        }
        //下面是驗證這個user是不是真實存在的
        String username = (String) JwtUtils.decodeToken(jwt).getAccount();//判斷數據庫中username是否存在
//        log.info("在使用token登陸"+username);
        return new SimpleAuthenticationInfo(jwt,jwt,getName());
        //這裏返回的是相似帳號密碼的東西,可是jwtToken都是jwt字符串。還須要一個該Realm的類名

    }
    //    受權
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {

        if (principalCollection == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }
        User user= JwtUtils.decodeToken(principalCollection.toString());

        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();

//            UserDto user = (UserDto) principalCollection.getPrimaryPrincipal();
            //從數據庫查詢角色
            Set<String> roleSet = new HashSet<>();
            roleSet.add("ghfhgfg");
            authorizationInfo.setRoles(roleSet);
//            //從數據庫查詢權限
            Set<String> permsSet = new HashSet<>();
                permsSet.add("/account/update/state");
            authorizationInfo.setStringPermissions(permsSet);

        return authorizationInfo;
    }
}

 

 

5、 shiro配置文件,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"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/util
       http://www.springframework.org/schema/util/spring-util-2.0.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd ">

    <!-- 配置 shiro 核心過濾器:shiroFilter -->
    <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
        <!-- 安全管理器 -->
        <property name="securityManager" ref="securityManager"/>
        <!--&lt;!&ndash; 未登錄跳轉的頁面 &ndash;&gt;-->
        <!--<property name="loginUrl" value="/login.jsp"/> -->
        <!--&lt;!&ndash; 受權失敗跳轉的頁面 &ndash;&gt;-->
        <!--<property name="unauthorizedUrl" value="/noauth.jsp"/> -->
        <property name="filters">
            <util:map>
                <entry key="jwt" value-ref="jwtFilter" />
                <!--<entry key="adminFilter" value-ref="adminFilter" />-->
            </util:map>
        </property>
        <!-- 配置 url 過濾規則 -->
        <property name="filterChainDefinitions">
            <value>
                <!-- 所有資源都須要認證才能訪問 -->
                /account/login=anon
                <!-- 用戶受權 -->
                <!--/account/**=adminFilter-->
                /timing/zeroTask=anon
                /**=jwt,roles[ghfhgfg1]
                <!--/**= roles[ghfhgfg]-->


            </value>
        </property>
    </bean>
    <!-- 安全管理器 -->

    <bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">
        <!-- 配置 realm -->
        <property name="realm" ref="bosRealm"/>
        <property name="subjectFactory" ref="subjectFactory"/>
        <property name="subjectDAO" ref="subjectDAO"/>
        <!--<property name="sessionManager" ref="sessionManager" />-->

    </bean>


    <!--&lt;!&ndash; 一、安全管理器 &ndash;&gt;-->
    <!--<bean id="securityManager" class="org.apache.shiro.web.mgt.DefaultWebSecurityManager">-->
        <!--<property name="realm" ref="shiroDbRealm"></property>-->
         <!--&lt;!&ndash; 設置緩存管理器爲 ehcache &ndash;&gt;-->
         <!--<property name="cacheManager" ref="shiroEhcacheManager"></property>-->
         <!--&lt;!&ndash; 配置sessionManager,提供session管理 &ndash;&gt;-->
         <!--<property name="sessionManager" ref="sessionManager"></property>-->
    <!--</bean>-->


    <!-- 自定義 realm -->
    <bean id="bosRealm" class="com.tg.higo.shiro.AdminShiroRealm"></bean>
    <!--<bean id="sessionManager"   class="com.tg.higo.shiro.CustomSessionManager">
    </bean>-->
    <bean id="subjectFactory" class="com.tg.higo.shiro.JwtDefaultSubjectFactory"></bean>
    <bean  id ="defaultSessionStorageEvaluator" class="org.apache.shiro.mgt.DefaultSessionStorageEvaluator">
        <property name="sessionStorageEnabled" value="false"></property>
    </bean>
    <bean  id ="subjectDAO" class="org.apache.shiro.mgt.DefaultSubjectDAO">
        <property name="sessionStorageEvaluator" ref="defaultSessionStorageEvaluator"/>
    </bean>

    <!-- 自定義攔截器 -->
    <bean id="jwtFilter" class="com.tg.higo.shiro.JwtFilter" />
    <!--<bean id="adminFilter" class="com.tg.higo.shiro.AdminAuthenticationFilter" />-->
    <!--<bean class="org.apache.shiro.spring.LifecycleBeanPostProcessor" id="lifecycleBeanPostProcessor" />-->
</beans>

 

 

6、總結安全

登陸不在走認證,之後每次要受權的接口,先走認證校驗是否有效,再去受權

相關文章
相關標籤/搜索