Shiro禁用Session,使用SSM+JWT+Shiro进行无状态RESTful API

本文源码:github

上一篇Shiro的demo:SpringMVC框架下使用shiro权限管理

最近一直在研究Shiro,因为项目里面要使用Shiro进行权限控制。由于项目不只是在Web端运行,还会在移动端App使用,想要使用JWT方式进行无状态的RESTful API交互,也就是登录后生成token并返回给前端,前端每次请求时都在请求头里面添加token,后端验证有效性。所以Shiro自带的Session要禁用掉,同时要重新写JWT的过滤器。

至于shiro的介绍,搬来官网介绍:https://shiro.apache.org/introduction.html

Shiro禁用Session,使用SSM+JWT+Shiro进行无状态RESTful API_第1张图片

如上介绍,由于使用JWT需要禁用session,所以该demo只是使用了认证Authentication和授权Authorization两大块。

一开始直接照着网上的例子做demo,一直出错。所以放弃shiro写了个拦截器用来拦截,只是没法细粒度的控制权限,如下:

package filter;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import util.JwtUtil;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class ControllerInterception extends HandlerInterceptorAdapter {
    private Logger logger = LoggerFactory.getLogger(ControllerInterception.class);

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        String url = request.getServletPath();
        logger.info("url"+url);
        String token = request.getHeader("token");
        logger.info("开始验证token");
        if (JwtUtil.verifyToken(token)) {
            logger.info("验证token通过");
            String role = (String) JwtUtil.parseToken(token).get("role");
            logger.info("role"+role);
            if (url.indexOf(role) == 1){
                logger.info("匹配url通过");
                return true;
            }
        }
        logger.info("验证token失败");
        response.sendRedirect(request.getContextPath()+"/login");
        return false;

    }
}


    
        
        
        
        
        
        
    

但是不想放弃使用shiro,所以自己一直在研究到底禁用session后shiro如何将JWT传递并在哪里校验。最终整理出来后才发现是如此的简单。说一下,使用shiro+ssm+jwt的一些前期准备:

  • 禁用session
  • JWT实现工具,这个网上可以找到很多,这里就不贴代码了
  • 自定义realm,继承AuthorizingRealm,重写认证和授权两个方法
  • 自定义filter,继承AccessControlFilter,重写方法isAccessAllowed,onAccessDenied

一、首先,禁用shiro的session,先添加shiro的依赖到pom



    org.apache.shiro
    shiro-core
    1.4.0


    org.apache.shiro
    shiro-web
    1.4.0


    org.apache.shiro
    shiro-spring
    1.4.0


    org.apache.shiro
    shiro-ehcache
    1.4.0

按照网上的说法要写一个类禁用(http://jinnianshilongnian.iteye.com/blog/2041909)

  1. public class StatelessDefaultSubjectFactory extends DefaultWebSubjectFactory {  
  2.     public Subject createSubject(SubjectContext context) {  
  3.         //不创建session  
  4.         context.setSessionCreationEnabled(false);  
  5.         return super.createSubject(context);  
  6.     }  
  7. }   

一开始就是按照这种写法,但是会报错,所以上面的类就省略了,但是不影响使用JWT。我是按下面的实现直接写在spring的xml文件中的,并没有实现上面的类:




    



    



    


    



    
    
    



    
    

 

二、其次:重写filter,看里面的注释,应该可以看懂:

package shiro;

import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.JwtUtil;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;

public class MyStatelessShiroFilter extends AccessControlFilter {
    private Logger logger = LoggerFactory.getLogger(MyStatelessShiroFilter.class);
    /**
     *返回false
     * @param servletRequest
     * @param servletResponse
     * @param o
     * @return 返回结果是false的时候才会执行下面的onAccessDenied方法
     * @throws Exception
     */
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object o) throws Exception {
        logger.info("is access allowed");
        return false;
    }

    /**
     * 从请求头获取token并验证,验证通过后交给realm进行登录
     * @param servletRequest
     * @param servletResponse
     * @return 返回结果为true表明登录通过
     * @throws Exception
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {
        logger.info("on access denied");
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        String jwt = request.getHeader("Authorization");
        if (JwtUtil.verifyToken(jwt)) {
            UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(jwt, jwt);
            try {
                //委托realm进行登录认证
                getSubject(servletRequest, servletResponse).login(usernamePasswordToken);
                return true;
            }catch (Exception e) {
                return false;
            }
        }
        redirectToLogin(servletRequest,servletResponse);
        return false;
    }

    /**
     * 重定向到登录页
     * @param request
     * @param response
     * @throws IOException
     */
    @Override
    protected void redirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
        logger.info("redirectToLogin");
        WebUtils.issueRedirect(request, response, "/login");
    }
}

这里面使用依然是shiro自带的token实现UsernamePasswordToken,自定义的token用来用去都用糊涂了,还是自带的好用一些。

这个时候就加入配置文件即可,statelessAuth就是自己的filter:




    
    
        
            
        
    
    
    
    
        
            /=anon
            /shirologin=anon
            /unauthorized=anon
            /user/**=statelessAuth,roles[user]
            /admin/**=statelessAuth,roles[admin]
            /**=statelessAuth
        
    

web.xml中也要配置filter,要和这里面的filter命名要一致:



    shiroFilter
    org.springframework.web.filter.DelegatingFilterProxy
    
        targetFilterLifecycle
        true
    


    shiroFilter
    /*

三、再次,就是自定义realm,实现认证和授权,这里的role我是写死的,省的查验数据库:

package shiro;

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.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class MyStatelessRealm extends AuthorizingRealm {
    Logger logger = LoggerFactory.getLogger(MyStatelessRealm.class);

    public boolean supports(AuthenticationToken token) {
        return token instanceof UsernamePasswordToken;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken) throws AuthenticationException {
        logger.info("Realm处理登录");
        UsernamePasswordToken usernamePasswordToken = (UsernamePasswordToken) authenticationToken;
        String token = usernamePasswordToken.getUsername();
        return new SimpleAuthenticationInfo(token, token, getName());
    }

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
        logger.info("Realm处理授权");
        String token = (String) principalCollection.getPrimaryPrincipal();
        logger.info("realm授权获取token:"+token);
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        authorizationInfo.addRole("user");
        return authorizationInfo;
    }

}

 

四、这时候就要写一些controller进行测试了,使用Json格式测试的:

package controller;

import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.subject.Subject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import util.JwtUtil;
import java.util.HashMap;
import java.util.Map;


@RestController
public class ShiroController {
    Logger logger = LoggerFactory.getLogger(ShiroController.class);

    @RequestMapping("shirologin")
    public String toLogin() {
        return "you must login firstly";
    }

    @RequestMapping(value = "shirologin",method = RequestMethod.POST)
    public String login(String username, String password) {
        logger.info("shiro登录");
        Map map = new HashMap<>();
        map.put("username",username);
        String token = new JwtUtil().createJwt(map);
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(token, token);
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(usernamePasswordToken);
        } catch (Exception e) {
            return "login fail ex:"+e.getMessage();
        }
        return "login success ! token is :"+token;
    }

    @RequestMapping("user")
    public String toUser(){
        logger.info("进入 user");
        return "welcome to user";
    }

    @RequestMapping("admin")
    public String toAdmin(){
        return "welcome to admin";
    }

    @RequestMapping("unauthorized")
    public String unAuth(){
        return "unauthorized";
    }
}

 

五、使用postman进行接口测试:

Shiro禁用Session,使用SSM+JWT+Shiro进行无状态RESTful API_第2张图片

Shiro禁用Session,使用SSM+JWT+Shiro进行无状态RESTful API_第3张图片

Shiro禁用Session,使用SSM+JWT+Shiro进行无状态RESTful API_第4张图片

Shiro禁用Session,使用SSM+JWT+Shiro进行无状态RESTful API_第5张图片

 

你可能感兴趣的:(shiro)