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、总结安全

登陆不在走认证,之后每次要受权的接口,先走认证校验是否有效,再去受权

相关文章
相关标签/搜索