【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成

1、POM文件中加入Shiro和fastJSON依赖

<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.2.3</version>
</dependency>

<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>fastjson</artifactId>
    <version>1.2.38</version>
</dependency>

2、加入几个HTTP和JSON相关的工具类

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.util;

import java.io.PrintWriter;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.ServletInputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LTHttpUtil {

    /**
     * 判断请求是否Ajax请求
     * 
     * @param request
     * @return
     */
    public static boolean isAjax(ServletRequest request) {
        String header = ((HttpServletRequest) request).getHeader("x-requested-with");
        if (header != null && header.equalsIgnoreCase("XMLHttpRequest")) {
            return true;
        }
        return false;
    }

    public static HttpServletRequest getRequest(ServletRequest request) {
        return new XssSqlHttpServletRequestWrapper((HttpServletRequest) request);
    }

    public static Map<String, String> getRequestHeaders(ServletRequest request) {
        Map<String, String> headerMap = new HashMap<>();
        @SuppressWarnings("rawtypes")
        Enumeration enums = LTHttpUtil.getRequest(request).getHeaderNames();
        while (enums.hasMoreElements()) {
            String name = (String) enums.nextElement();
            String value = LTHttpUtil.getRequest(request).getHeader(name);
            if (null != value && !"".equals(value)) {
                headerMap.put(name, value);
            }
        }
        return headerMap;
    }

    /**
     * 读取ServletRequest请求的正文
     * 
     * @param request
     * @return
     */
    @SuppressWarnings("unchecked")
    public static Map<String, String> getRequestBodyMap(ServletRequest request) {
        // 通过ServletRequest.getInputStream()可以获取到请求的正文
        // 然后放置到请求的body变量中,方便在该请求的生命周期中使用,也避免多次读取
        Map<String, String> dataMap = new HashMap<>();

        try {
            if (request.getAttribute("body") != null) {
                dataMap = (Map<String, String>) request.getAttribute("body");
            } else {
                // 原因https://www.cnblogs.com/wtstengshen/p/3186530.html
                // 因为ServletRequest的InputStream只能读取一次,所以登录的时候,这里读取了,登录方法就不能使用@RequestBody了
                ServletInputStream steam = request.getInputStream();
                Map<String, String> maps = LTJSON.parseObject(steam, Map.class);
                dataMap.putAll(maps);
                System.out.println(dataMap);
                request.setAttribute("body", dataMap);
            }
        } catch (Exception e) {
            System.out.println(e.getMessage());
        }
        return dataMap;
    }

    /**
     * 如果是POST请求,且URL地址结尾是/login(不分大小写),则返回true
     * 
     * @param request
     * @return
     */
    public static boolean isLoginPost(ServletRequest request) {
        // 然后放置到请求的isLoginPost变量中,方便在该请求的生命周期中使用,也避免多次读取
        boolean isLoginPost = false;
        if (request.getAttribute("isLoginPost") != null) {
            isLoginPost = (boolean) request.getAttribute("isLoginPost");
        } else {
            HttpServletRequest httpRequest = (HttpServletRequest) request;
            String url = httpRequest.getRequestURL().toString();
            url = url.substring(url.lastIndexOf("/") + 1, url.length()).toUpperCase();
            String method = httpRequest.getMethod().toUpperCase();
            if (url.equals("LOGIN") && method.equals("POST"))
                isLoginPost = true;
            request.setAttribute("isLoginPost", isLoginPost);
        }
        return isLoginPost;
    }

    /**
     * 将过滤器中产生的异常以Json的形式返回给客户端
     * 
     * @param response
     * @param obj
     */
    public static void ResponseWrite(ServletResponse response, Object obj) {
        PrintWriter out = null;
        try {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            httpResponse.setStatus(401);
            httpResponse.setCharacterEncoding("UTF-8");
            httpResponse.setContentType("application/json");
            out = httpResponse.getWriter();
            out.println(LTJSON.toJSONString(obj));
        } catch (Exception e) {
            System.out.println(e);
        } finally {
            if (null != out) {
                out.flush();
                out.close();
            }
        }
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Type;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.parser.Feature;
import com.alibaba.fastjson.serializer.SerializerFeature;

/**
 * @author Yang
 * @description 用于对JSON操作做一个封装,方便以后更换修改
 */
public class LTJSON {

    /**
     * 对象转换为JSON
     * 
     * @param object
     * @return
     */
    public static final String toJSONString(Object object) {
        /*
         * QuoteFieldNames———-输出key时是否使用双引号,默认为true
         * WriteMapNullValue——–是否输出值为null的字段,默认为false
         * WriteNullNumberAsZero—-数值字段如果为null,输出为0,而非null
         * WriteNullListAsEmpty—–List字段如果为null,输出为[],而非null
         * WriteNullStringAsEmpty—字符类型字段如果为null,输出为"",而非null
         * WriteNullBooleanAsFalse–Boolean字段如果为null,输出为false,而非null
         */
        return JSONObject.toJSONString(object, SerializerFeature.WriteMapNullValue,
                SerializerFeature.WriteNullStringAsEmpty, SerializerFeature.WriteNullNumberAsZero,
                SerializerFeature.WriteNullListAsEmpty, SerializerFeature.WriteNullBooleanAsFalse);
    }

    /**
     * 输入流转换为对象
     * 
     * @param is
     * @param type
     * @param features
     * @return
     * @throws IOException
     */
    @SuppressWarnings("unchecked")
    public static <T> T parseObject(InputStream is, //
            Type type, //
            Feature... features) throws IOException {
        T parseObject = (T) JSON.parseObject(is, type, features);
        return parseObject;
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.util;

import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;

/* *
 * @Author tomsun28
 * @Description request请求安全过滤包装类
 * @Date 20:41 2018/4/15
 */
public class XssSqlHttpServletRequestWrapper extends HttpServletRequestWrapper {



    public XssSqlHttpServletRequestWrapper(HttpServletRequest request) {
        super(request);
    }

    /* *
     * @Description 重写  数组参数过滤
     * @Param [parameter]
     * @Return java.lang.String[]
     */
    @Override
    public String[] getParameterValues(String parameter) {
        String[] values = super.getParameterValues(parameter);
        if (values == null) {
            return null;
        }
        int count = values.length;
        String[] encodedValues = new String[count];
        for (int i = 0 ; i < count ; i++ ) {
            encodedValues[i] = filterParamString(values[i]);
        }
        return encodedValues;
    }

    @Override
    public Map<String,String[]> getParameterMap() {
        Map<String,String[]> primary = super.getParameterMap();
        Map<String,String[]> result = new HashMap<>();
        for (Map.Entry<String,String[]> entry : primary.entrySet()) {
            result.put(entry.getKey(),filterEntryString(entry.getValue()));
        }
        return result;
    }

    @Override
    public String getParameter(String parameter) {
        return filterParamString(super.getParameter(parameter));
    }

    @Override
    public String getHeader(String name) {
        return filterParamString(super.getHeader(name));
    }

    @Override
    public Cookie[] getCookies() {
        Cookie[] cookies = super.getCookies();
        if (cookies != null) {
            for (int i = 0 ; i < cookies.length; i++) {
                Cookie cookie = cookies[i];
                cookie.setValue(filterParamString(cookie.getValue()));
            }
        }
        return cookies;
    }

    /* *
     * @Description  过滤字符串数组不安全内容
     * @Param [value]
     * @Return java.lang.String[]
     */
    private String[] filterEntryString(String[] value) {
        for (int i = 0; i < value.length; i++) {
            value[i] = filterParamString(value[i]);
        }
        return value;
    }

    /* *
     * @Description 过滤字符串不安全内容
     * @Param [value]
     * @Return java.lang.String
     */
    private String filterParamString(String value) {
        if (null == value) {
            return null;
        }
        // 过滤XSS 和 SQL 注入
        return XssUtil.stripSqlXss(value);
    }

}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.util;

import java.util.regex.Pattern;

/* *
 * @Author tomsun28
 * @Description Web防火墙工具类
 * @Date 19:51 2018/4/15
 */
public class XssUtil {

    /* *
     * @Description 过滤XSS脚本内容
     * @Param [value]
     * @Return java.lang.String
     */
    public static String stripXSS(String value) {
        String rlt = null;

        if (null != value) {
            // NOTE: It's highly recommended to use the ESAPI library and uncomment the following line to
            // avoid encoded attacks.
            // value = ESAPI.encoder().canonicalize(value);

            // Avoid null characters
            rlt = value.replaceAll("", "");

            // Avoid anything between script tags
            Pattern scriptPattern = Pattern.compile("<script>(.*?)</script>", Pattern.CASE_INSENSITIVE);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            // Avoid anything in a src='...' type of expression
            /*scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\'(.*?)\\\'", Pattern.CASE_INSENSITIVE
                    | Pattern.MULTILINE | Pattern.DOTALL);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            scriptPattern = Pattern.compile("src[\r\n]*=[\r\n]*\\\"(.*?)\\\"", Pattern.CASE_INSENSITIVE
                    | Pattern.MULTILINE | Pattern.DOTALL);
            rlt = scriptPattern.matcher(rlt).replaceAll("");*/

            // Remove any lonesome </script> tag
            scriptPattern = Pattern.compile("</script>", Pattern.CASE_INSENSITIVE);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            // Remove any lonesome <script ...> tag
            scriptPattern = Pattern.compile("<script(.*?)>", Pattern.CASE_INSENSITIVE
                    | Pattern.MULTILINE | Pattern.DOTALL);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            // Avoid eval(...) expressions
            scriptPattern = Pattern.compile("eval\\((.*?)\\)", Pattern.CASE_INSENSITIVE
                    | Pattern.MULTILINE | Pattern.DOTALL);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            // Avoid expression(...) expressions
            scriptPattern = Pattern.compile("expression\\((.*?)\\)", Pattern.CASE_INSENSITIVE
                    | Pattern.MULTILINE | Pattern.DOTALL);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            // Avoid javascript:... expressions
            scriptPattern = Pattern.compile("javascript:", Pattern.CASE_INSENSITIVE);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            // Avoid vbscript:... expressions
            scriptPattern = Pattern.compile("vbscript:", Pattern.CASE_INSENSITIVE);
            rlt = scriptPattern.matcher(rlt).replaceAll("");

            // Avoid οnlοad= expressions
            scriptPattern = Pattern.compile("onload(.*?)=", Pattern.CASE_INSENSITIVE
                    | Pattern.MULTILINE | Pattern.DOTALL);
            rlt = scriptPattern.matcher(rlt).replaceAll("");
        }

        return rlt;
    }

    /* *
     * @Description 过滤SQL注入内容
     * @Param [value]
     * @Return java.lang.String
     */
    public static String stripSqlInjection(String value) {
        return (null == value) ? null : value.replaceAll("('.+--)|(--)|(%7C)", ""); //value.replaceAll("('.+--)|(--)|(\\|)|(%7C)", "");
    }

    /* *
     * @Description 过滤SQL 和 XSS注入内容
     * @Param [value]
     * @Return java.lang.String
     */
    public static String stripSqlXss(String value) {
        return stripXSS(stripSqlInjection(value));
    }

}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.util;

import java.util.UUID;

public class LTUUID {
    public static String getUUID32(){
        String uuid = UUID.randomUUID().toString().replace("-", "").toLowerCase();
        return uuid;
    }
}
View Code

3、创建Shiro所需的一些基础类

a、Shiro用户模型

简单的一个用户对象,避免Shiro与数据库用户实体耦合

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
public class ShiroUser {
    private String username;
    private String password;

    public ShiroUser() {
    }

    public ShiroUser(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
View Code

b、Shiro Token令牌

在Shiro中,处理认证时,一般是使用Token令牌,传送给Realm来进行处理

用户名密码令牌

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
public class MyUsernamePasswordToken extends UsernamePasswordToken {

    private static final long serialVersionUID = 8505830411513785701L;

    public MyUsernamePasswordToken(String username, String pswd) {
        super(username, pswd);
        this.pswd = pswd;
    }

    private String pswd;

    public String getPswd() {
        return pswd;
    }

    public void setPswd(String pswd) {
        this.pswd = pswd;
    }

}
View Code

Web Token令牌:因为是RESTful风格的框架,在用户登录后会生成一个用户凭证,用户访问其它功能时,每次都要把这个凭证带上,才能知道他是该用户,这个Web Token令牌是用来处理这种凭证的

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.token;

import org.apache.shiro.authc.AuthenticationToken;

/* *
 * @Author tomsun28
 * @Description JWT token
 * @Date 19:37 2018/2/10
 */
public class MyWebJsonToken implements AuthenticationToken {

    private static final long serialVersionUID = 492511894487921685L;

    private String username; // 用户名
    private String userip; // 用户IP
    private String userdevice; // 用户设备信息
    private String usertoken; // 用户json web token值

    public MyWebJsonToken(String ip, String deivce, String token, String username) {
        this.userip = ip;
        this.userdevice = deivce;
        this.usertoken = token;
        this.username = username;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getUserip() {
        return userip;
    }

    public void setUserip(String userip) {
        this.userip = userip;
    }

    public String getUserdevice() {
        return userdevice;
    }

    public void setUserdevice(String userdevice) {
        this.userdevice = userdevice;
    }

    public String getUsertoken() {
        return usertoken;
    }

    public void setUsertoken(String usertoken) {
        this.usertoken = usertoken;
    }

    @Override
    public Object getPrincipal() {
        return username;
    }

    @Override
    public Object getCredentials() {
        return usertoken;
    }
}
View Code

c、用户缓存

当一个用户登录后,会用户名和凭证缓存下来,那么用户带上凭证访问其它功能时,就能判断该用户是否已经登录

这里使用一个静态变量来简化处理,其它更好的方式可以自行改造(如使用redis缓存等)

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.cache;

import java.util.HashMap;
import java.util.Map;

//应该使用接口管理Shiro处理数据的部分
public class MyShiroCache {
    // 已登录的用户
    private static Map<String, String> onlineUsers = new HashMap<String, String>();

    public static Map<String, String> getOnlineUsers() {
        return onlineUsers;
    }

    public static void setOnlineUsers(Map<String, String> onlineUsers) {
        MyShiroCache.onlineUsers = onlineUsers;
    }

    public static String getUserNameByToken(String userToken) {
        for (String key : onlineUsers.keySet()) {
            System.out.println("key:"+key);
            System.out.println("tokken:"+onlineUsers.get(key));
            if (onlineUsers.get(key).equals(userToken))
                return key;
        }
        return null;
    }
}
View Code

d、数据服务

判断一个用户是否帐号密码正确,或者用户是否有权限访问某个功能

这里定义几个接口,实现的话,我这里是随便写了几个假定实现,请自行完善

数据模型

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.bs.system.model;

public class User {
    private String username;
    private String password;

    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
View Code

数据接口

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.bs.system.service;

import com.ltsolution.framework.bs.system.model.User;

public interface UserService {
    public User getUserByUserName(String username);

    public boolean isPermitted(String url, String method, String username);

}
View Code

数据接口实现

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.bs.system.service.impl;

import org.springframework.stereotype.Service;

import com.ltsolution.framework.bs.system.model.User;
import com.ltsolution.framework.bs.system.service.UserService;

@Service
public class UserServiceImpl implements UserService {
    public User getUserByUserName(String username) {
        if (username != null && username.equals("yang"))
            return new User(username, "1234");
        else
            return null;
    }

    @Override
    public boolean isPermitted(String url, String method, String username) {

        System.out.println("【UserServiceImpl】url:" + url);
        System.out.println("【UserServiceImpl】method:" + method);
        System.out.println("【UserServiceImpl】username:" + username);
        // 根据用户,找角色,找权限,然后找到这些权限中,第一个斜杠前和url相等,且斜杠数量相同的部分

        // 然后循环这些url

        // --去掉url中的/*,然后和访问的url对比

        // --找到能匹配和方法相同的,就代表有权限了

        return false;
    }
}
View Code

shiro接口

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.service;

import com.ltsolution.framework.shiro.model.ShiroUser;

public interface ShiroAuthcService {
    public ShiroUser getShiroUserByName(String username);

    public ShiroUser getShiroUserByToken(String token);
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.service;

public interface ShiroAuthorService {
    public boolean isPermitted(String url, String method, String username);
}
View Code

shiro实现

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.ltsolution.framework.bs.system.model.User;
import com.ltsolution.framework.bs.system.service.UserService;
import com.ltsolution.framework.shiro.cache.MyShiroCache;
import com.ltsolution.framework.shiro.model.ShiroUser;
import com.ltsolution.framework.shiro.service.ShiroAuthcService;

@Service
public class ShiroAuthcServiceImpl implements ShiroAuthcService {

    @Autowired
    UserService userService;

    public ShiroUser getShiroUserByName(String username) {
        User user = userService.getUserByUserName(username);
        if (user != null) {
            ShiroUser shiroUser = new ShiroUser();
            shiroUser.setUsername(user.getUsername());
            shiroUser.setPassword(user.getPassword());
            return shiroUser;
        } else
            return null;
    }

    @Override
    public ShiroUser getShiroUserByToken(String token) {
        String username = MyShiroCache.getUserNameByToken(token);
        return getShiroUserByName(username);
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.service.impl;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.ltsolution.framework.bs.system.service.UserService;
import com.ltsolution.framework.shiro.service.ShiroAuthorService;

@Service
public class ShiroAuthorServiceImpl implements ShiroAuthorService {

    @Autowired
    UserService userService;

    public boolean isPermitted(String url, String method, String username) {
        return userService.isPermitted(url, method, username);
    }
}
View Code

 4、集成Shiro的思路

这里没有使用Shiro的一般做法,所以大家自行权宜此方案是否合适

思路:

a、创建2个过滤器,分别为:登录认证过滤器,授权过滤器

b、设置登录方法需要经过登录认证过滤器,其它方法需要经过授权过滤器

c、当登录方法经过登录认证过滤器时,进入登录认证Realm处理,如果确认帐号密码成功,则返回用户的凭证到前台;否则直接返回错误信息到前台。其实这里这样处理的话,连登录控制器都不需要,直接在过滤器中返回数据即可。

d、当前台带凭证访问后台URL时,会被权限过滤器所截获,然后先判断这个凭证是否有效;有效,则访问数据库查看该用户是否有此URL的权限,如果有权限,则进入控制器方法;如果凭证无效或者没有权限,则直接返回错误信息。

Realm设计:2个Realm,一个用于认证,一个用于授权

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.realm;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

import com.ltsolution.framework.shiro.token.MyUsernamePasswordToken;

public class AuthcRealmMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {

        System.out.println("启动密码对比器。");
        MyUsernamePasswordToken mytoken = (MyUsernamePasswordToken) token;

        System.out.println(mytoken.getPswd());
        if (mytoken.getPswd() != null && mytoken.getPswd().equals((String)info.getCredentials())) {
            System.out.println("密码对比器对比成功。");
            return true;
        } else {
            throw new CredentialsException();
        }
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.realm;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.ltsolution.framework.shiro.model.ShiroUser;
import com.ltsolution.framework.shiro.service.ShiroAuthcService;
import com.ltsolution.framework.shiro.token.MyUsernamePasswordToken;

@Component
public class AuthcRealm extends AuthorizingRealm {

    @Autowired
    ShiroAuthcService shiroAuthcService;

    public AuthcRealm() {
        super();
        this.setCredentialsMatcher(new AuthcRealmMatcher());
    }

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

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof MyUsernamePasswordToken;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
        System.out.println("检查用户的是否已认证!");

        MyUsernamePasswordToken token = (MyUsernamePasswordToken) authcToken;

        String username = token.getUsername();
        System.out.println("userName:" + username);
        String password = token.getPswd();
        System.out.println("password:" + password);

        ShiroUser user = shiroAuthcService.getShiroUserByName(username);

        if (user == null) {
            System.out.println("抛出异常UnknownAccountException");
            throw new UnknownAccountException();
        }

        // //可以更新用户登录时间等操作
        // System.out.println("进行用户登录时间更新等操作");

        // 返回一个认证信息,代表认证成功
        System.out.println("Realm认证成功!");
        return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), this.getName());
    }

    /**
     * 授权
     */
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        return info;
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.realm;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;

public class AuthorRealmMatcher extends SimpleCredentialsMatcher {

    @Override
    public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
        //直接return true,代表授权过滤器,不需要realm的密码对比器来控制认证
        return true;
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.realm;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import com.ltsolution.framework.shiro.cache.MyShiroCache;
import com.ltsolution.framework.shiro.model.ShiroUser;
import com.ltsolution.framework.shiro.service.ShiroAuthcService;
import com.ltsolution.framework.shiro.token.MyWebJsonToken;

@Component
public class AuthorRealm extends AuthorizingRealm {

    @Autowired
    ShiroAuthcService shiroAuthcService;

    public AuthorRealm() {
        super();
        this.setCredentialsMatcher(new AuthorRealmMatcher());
    }

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

    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof MyWebJsonToken;
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authcToken) {
        System.out.println("检查用户的是否已认证!");

        MyWebJsonToken myWebJsonToken = (MyWebJsonToken) authcToken;

        String userToken = myWebJsonToken.getUsertoken();
        System.out.println("usertoken:" + userToken);
        String userName = MyShiroCache.getUserNameByToken(userToken);
        System.out.println("MyShiroCache:" + MyShiroCache.getOnlineUsers());
        System.out.println("userName:" + userName);
        ShiroUser user = shiroAuthcService.getShiroUserByName(userName);

        if (user == null)
        {
            System.out.println("找不到帐号");
            throw new UnknownAccountException();
        }

        // //可以更新用户登录时间等操作
        // System.out.println("进行用户登录时间更新等操作");

        // 返回一个认证信息,代表认证成功
        System.out.println("Realm认证成功!");
        return new SimpleAuthenticationInfo(user.getUsername(), user.getPassword(), this.getName());
    }
    
    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
        return info;
    }
}
View Code

过滤器设计:2个过滤器,一个用于登录URL,一个用于其他URL检测授权

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.filter;

import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.authc.CredentialsException;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;

import com.ltsolution.framework.common.msgmodel.AppResult;
import com.ltsolution.framework.shiro.cache.MyShiroCache;
import com.ltsolution.framework.shiro.token.MyUsernamePasswordToken;
import com.ltsolution.framework.util.LTHttpUtil;
import com.ltsolution.framework.util.LTUUID;

public class AuthcFilter extends AccessControlFilter {
    final static Class<AuthcFilter> CLASS = AuthcFilter.class;

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // isAccessAllowed 此方法用于判断访问是否允许
        // return true 允许访问→进入控制器方法
        // return false 不允许访问→进入onAccessDenied方法

        System.out.println("认证过滤器");

        // Subject subject = getSubject(request, response);
        // if(subject)
        // return subject.isAuthenticated();
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // onAccessDenied 此方法二次判断访问是否允许
        // return true 允许访问→进入控制器方法
        // return false 不允许访问→返回空响应

        System.out.println("认证过滤器失败处理");
        // 如果是登录操作,则进行登录认证
        // 认证成功则返回true,进入登录控制器
        // 认证失败则返回失败信息的响应
        if (LTHttpUtil.isLoginPost(request)) {
            // 获取Shiro用户
            Subject subject = getSubject(request, response);
            try {
                // 获取请求传输的帐号密码
                Map<String, String> bodyMap = LTHttpUtil.getRequestBodyMap(request);
                String username = bodyMap.get("username");
                String password = bodyMap.get("password");
                // 生成一个令牌,进行登录认证
                MyUsernamePasswordToken token = new MyUsernamePasswordToken(username, password);
                // 调用login方法时,Shiro会调用配置好的Realm的doGetAuthenticationInfo方法进行认证
                subject.login(token);
                
                //登录成功,创建一个WebToken
                String uuid = LTUUID.getUUID32();
                MyShiroCache.getOnlineUsers().put(username,uuid );
                LTHttpUtil.ResponseWrite(response, new AppResult().ok("登录成功!").addData("token", uuid));
                
                // 如果Realm的doGetAuthenticationInfo方法认证成功,则返回true,进入控制器方法
                System.out.println("过滤器登录成功!");
                return false;
                // 如果Realm的doGetAuthenticationInfo方法认证失败,会抛出异常
                // 我们对异常进行截获,然后在响应信息中加入需要提示的数据,并返回false
            } catch (UnknownAccountException e) {
                // 当使用多个Realm的时候吗,当一个Reaml抛出以上时会继续下个Realm,最终抛出AuthenticationException,而Realm内部的异常截获不到
                System.out.println("Authc过滤器截获UnknownAccountException异常!");
                LTHttpUtil.ResponseWrite(response, new AppResult().error("账号不存在!"));
                return false;
            } catch (CredentialsException e) {
                // 当使用多个Realm的时候吗,当一个Reaml抛出以上时会继续下个Realm,最终抛出AuthenticationException,而Realm内部的异常截获不到
                System.out.println("Authc过滤器截获CredentialsException异常!");
                LTHttpUtil.ResponseWrite(response, new AppResult().error("密码错误!"));
                return false;
            } catch (Exception e) {
                System.out.println("Authc过滤器截获" + e.getClass().getTypeName() + "异常!");
                LTHttpUtil.ResponseWrite(response,
                        new AppResult().error(e.getClass().getTypeName() + " " + e.getMessage()));
                return false;
            }
        }
        return false;
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.filter;

import java.util.Map;

import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;

import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.filter.AccessControlFilter;
import org.apache.shiro.web.util.WebUtils;

import com.ltsolution.framework.common.msgmodel.AppResult;
import com.ltsolution.framework.shiro.service.ShiroAuthorService;
import com.ltsolution.framework.shiro.token.MyWebJsonToken;
import com.ltsolution.framework.util.LTHttpUtil;

public class AuthorFilter extends AccessControlFilter {
    final static Class<AuthorFilter> CLASS = AuthorFilter.class;

    private ShiroAuthorService shiroAuthorService;

    @Override
    protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) {
        // isAccessAllowed 此方法用于判断访问是否允许
        // return true 允许访问→进入控制器方法
        // return false 不允许访问→进入onAccessDenied方法

        System.out.println("权限过滤器");

        // Subject subject = getSubject(request, response);
        // if(subject)
        // return subject.isAuthenticated();
        return false;
    }

    @Override
    protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
        // onAccessDenied 此方法二次判断访问是否允许
        // return true 允许访问→进入控制器方法
        // return false 不允许访问→返回空响应

        System.out.println("权限过滤器失败处理");

        // 检查当前token是否有对应的已登录用户
        Subject subject = getSubject(request, response);
        try {
            // --获取json token
            Map<String, String> maps = LTHttpUtil.getRequestHeaders(request);
            // 获取请求头部所带的信息,主要是token
            String userip = request.getRemoteAddr();
            String userdevice = maps.get("userdevice");
            String usertoken = maps.get("usertoken");
            String username = maps.get("usaername");
            // 生成一个令牌,进行登录认证
            MyWebJsonToken token = new MyWebJsonToken(userip, userdevice, usertoken, username);
            // 调用login方法时,Shiro会调用配置好的Realm的doGetAuthenticationInfo方法进行认证
            subject.login(token);
            // 如果Realm的doGetAuthenticationInfo方法认证成功,则返回true,进入控制器方法
            System.out.println("过滤器登录成功!");
            // return true;
            // 如果Realm的doGetAuthenticationInfo方法认证失败,会抛出异常
        } catch (UnknownAccountException ex) {
            System.out.println("Author过滤器截获UnknownAccountException异常!");
            LTHttpUtil.ResponseWrite(response, new AppResult().error("用户未登录!"));
            return false;
        } catch (Exception ex) {
            System.out.println("Author过滤器截获Exception异常!");
            LTHttpUtil.ResponseWrite(response, new AppResult().error(ex.getMessage()));
            return false;
        }

        // 判断是否授权
        String url = this.getPathWithinApplication(request).toLowerCase();
        System.out.println("【AuthorFilter】url:" + url);
        String method = WebUtils.toHttp(request).getMethod().toUpperCase();
        System.out.println("【AuthorFilter】method:" + method);
        String principal = subject.getPrincipal().toString();
        System.out.println("【AuthorFilter】principal:" + principal);
        System.out.println("【AuthorFilter】shiroAuthorService:" + shiroAuthorService);
        if (subject == null || !shiroAuthorService.isPermitted(url, method, principal)) {
            System.out.println("【AuthorFilter】发现用户未授权!");
            LTHttpUtil.ResponseWrite(response, new AppResult().error("当前用户没有此权限!"));
            return false;
        }
        System.out.println("【AuthorFilter】发现用户已授权!");
        // 过滤成功,进入控制器
        return true;
    }
    
    
    public void setShiroAuthorService(ShiroAuthorService shiroAuthorService)
    {
        this.shiroAuthorService = shiroAuthorService;
    }
}
View Code

最后就是设置Shiro的配置类

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.config;

import java.util.Collection;

import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.pam.AuthenticationStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.realm.Realm;

/* *
 * @Author tomsun28
 * @Description 
 * @Date 21:15 2018/3/3
 */
public class MyModularRealmAuthenticator extends ModularRealmAuthenticator {

    @Override
    //原方法中没有抛出异常,这里改成抛出异常
    protected AuthenticationInfo doMultiRealmAuthentication(Collection<Realm> realms, AuthenticationToken token) {

        AuthenticationStrategy strategy = getAuthenticationStrategy();

        AuthenticationInfo aggregate = strategy.beforeAllAttempts(realms, token);

        for (Realm realm : realms) {

            aggregate = strategy.beforeAttempt(realm, token, aggregate);

            if (realm.supports(token)) {

                AuthenticationInfo info = null;
                Throwable t = null;
                info = realm.getAuthenticationInfo(token);

                aggregate = strategy.afterAttempt(realm, token, info, aggregate, t);

            } else {
            }
        }

        aggregate = strategy.afterAllAttempts(token, aggregate);

        return aggregate;
    }
}
View Code
【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成
package com.ltsolution.framework.shiro.config;

import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.Realm;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.ltsolution.framework.shiro.filter.AuthcFilter;
import com.ltsolution.framework.shiro.filter.AuthorFilter;
import com.ltsolution.framework.shiro.realm.AuthcRealm;
import com.ltsolution.framework.shiro.realm.AuthorRealm;
import com.ltsolution.framework.shiro.service.ShiroAuthorService;

@Configuration
public class ShiroConfiguration {
    @Autowired
    AuthcRealm authcRealm;
    @Autowired
    AuthorRealm authorRealm;
    @Autowired
    ShiroAuthorService shiroAuthorService;

    @Bean
    public SecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();

        // 修改了一下验证器,在多Realm下正常处理自定义异常
        // 必须放在setRealms前面,不然无效
        ModularRealmAuthenticator authenticator = new MyModularRealmAuthenticator();
        securityManager.setAuthenticator(authenticator);

        Collection<Realm> realms = new ArrayList<Realm>();
        realms.add(authcRealm);
        realms.add(authorRealm);
        securityManager.setRealms(realms);

        // securityManager.setRealm(authcRealm);

        return securityManager;
    }

    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        // 添加自定义过滤器,用于下面过滤器链的配置
        Map<String, Filter> filterMap = shiroFilterFactoryBean.getFilters();
        
        filterMap.put("LTAuthc", new AuthcFilter());
        
        AuthorFilter LTAuthorFilter = new AuthorFilter();
        LTAuthorFilter.setShiroAuthorService(shiroAuthorService);
        filterMap.put("LTAuthor", LTAuthorFilter);
        
        shiroFilterFactoryBean.setFilters(filterMap);

        // 配置过滤器链
        // 使用LinkedHashMap,是一个有序列表,判断一个URL的过滤器时,只找符合条件的第一个过滤器,后面的过滤器都忽略
        // 还要注意的一点是,put方法不要设置相同的Key,因为会替代前面相同的Key,使前面相同的Key无效,且影响阅读和理解
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();

        // /**放到最后,如果放第一,那么所有URL的过滤器都是找到这个过滤器了,后面的过滤器都失效
        // 标明所有功能都需要认证才能登陆
        // 登录方法,通过认证条件:帐号密码符合要求;返回值是token
        filterChainDefinitionMap.put("/login", "LTAuthc");
        // 其它页面,需要授权:根据请求头部的token信息,查找相应的权限并对比
        filterChainDefinitionMap.put("/**", "LTAuthor");

        // 本系统基于REST,所以不需要返回页面
        // // 如果不设置默认会自动寻找Web工程根目录下的"/login.jsp"页面
        // shiroFilterFactoryBean.setLoginUrl("/login");
        // // 登录成功后要跳转的链接
        // shiroFilterFactoryBean.setSuccessUrl("/index");
        // // 未授权界面;
        // shiroFilterFactoryBean.setUnauthorizedUrl("/403");

        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        return shiroFilterFactoryBean;
    }
}
View Code

5、测试

登录:

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成

访问其他URL:

【从0到1,搭建Spring Boot+RESTful API+Shiro+Mybatis+SQLServer权限系统】05、Shiro集成