1. 程式人生 > >Spring boot整合shiro使用Ajax方式,最詳細教程

Spring boot整合shiro使用Ajax方式,最詳細教程

最近一直在自己的個人專案中整合進shiro這個許可權控制框架,踩了不少的坑,sb(允許我這麼叫他把,方便簡潔)整合shiro的教程不少,但是使用ajax方式的還真的不是很多,下面把我自己的經驗分享給大家。

1、在pom中加入shiro的包

 <!-- shiro許可權控制 -->
<dependency>
    <groupId>org.apache.shiro</groupId>
    <artifactId>shiro-spring</artifactId>
    <version>1.4.0</version
>
</dependency>

2、首先建立實體
這裡寫圖片描述

一共是這三個實體

這裡是UserEntity

package com.cy.example.entity;

import java.util.List;

import org.springframework.stereotype.Repository;

@Repository
public class UserEntity extends SuperEntity {

    private String c_username;

    private String c_pwd;

    private
String c_phone; private String n_age; private String n_sex; private int n_status; private List<SysRoleEntity> roleList;// 一個使用者具有多個角色 //getter setter 省略,以下2個實體也是 public byte[] getCredentialsSalt() { // TODO Auto-generated method stub return this.c_username.getBytes(); } }

SysRoleEntity

package com.cy.example.entity;

import java.util.ArrayList;
import java.util.List;

public class SysRoleEntity extends SuperEntity {

    private String c_roleName;

    private List<SysPermisEntity> permisList;// 一個角色對應多個許可權

    private List<UserEntity> userList;// 一個角色對應多個使用者


    public List<String> getPermissionsName() {
        List<String> list = new ArrayList<String>();
        List<SysPermisEntity> perlist = getPermisList();
        for (SysPermisEntity per : perlist) {
            list.add(per.getC_permisName());
        }
        return list;
    }

    @Override
    public String toString() {
        return "SysRoleEntity [c_roleName=" + c_roleName + ", permisList="
                + permisList + ", userList=" + userList + "]";
    }

}

SysPermisEntity

package com.cy.example.entity;

import java.util.List;

public class SysPermisEntity extends SuperEntity {

    private String c_permisName;

    private List<SysRoleEntity> roles;// 一個許可權對應一個角色



}

3、資料庫準備
這裡寫圖片描述

這裡多了2個表,一個使用者關聯角色表,一個是角色關聯許可權表
表結構和資料直接看sql吧,

DROP TABLE IF EXISTS `sys_permission`;
CREATE TABLE `sys_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c_permisName` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_permission
-- ----------------------------
INSERT INTO `sys_permission` VALUES ('1', 'add');
INSERT INTO `sys_permission` VALUES ('2', 'del');
INSERT INTO `sys_permission` VALUES ('3', 'update');
INSERT INTO `sys_permission` VALUES ('4', 'list');
INSERT INTO `sys_permission` VALUES ('5', 'user:list');
INSERT INTO `sys_permission` VALUES ('6', 'user:update');

-- ----------------------------
-- Table structure for sys_roles
-- ----------------------------
DROP TABLE IF EXISTS `sys_roles`;
CREATE TABLE `sys_roles` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `c_roleName` varchar(255) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_roles
-- ----------------------------
INSERT INTO `sys_roles` VALUES ('1', 'admin');
INSERT INTO `sys_roles` VALUES ('2', 'manege');
INSERT INTO `sys_roles` VALUES ('3', 'normal');

-- ----------------------------
-- Table structure for sys_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `sys_role_permission`;
CREATE TABLE `sys_role_permission` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT,
  `n_permission_id` bigint(20) NOT NULL,
  `n_role_id` bigint(20) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_role_permission
-- ----------------------------
INSERT INTO `sys_role_permission` VALUES ('3', '3', '1');
INSERT INTO `sys_role_permission` VALUES ('4', '4', '1');
INSERT INTO `sys_role_permission` VALUES ('5', '1', '2');
INSERT INTO `sys_role_permission` VALUES ('6', '2', '2');
INSERT INTO `sys_role_permission` VALUES ('7', '3', '2');
INSERT INTO `sys_role_permission` VALUES ('8', '4', '2');
INSERT INTO `sys_role_permission` VALUES ('9', '3', '3');
INSERT INTO `sys_role_permission` VALUES ('10', '1', '1');

-- ----------------------------
-- Table structure for sys_user_role
-- ----------------------------
DROP TABLE IF EXISTS `sys_user_role`;
CREATE TABLE `sys_user_role` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `n_userId` int(11) NOT NULL,
  `n_roleId` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of sys_user_role
-- ----------------------------
INSERT INTO `sys_user_role` VALUES ('1', '8', '1');

-- ----------------------------
-- Table structure for users
-- ----------------------------
DROP TABLE IF EXISTS `users`;
CREATE TABLE `users` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT,
  `c_username` varchar(255) NOT NULL,
  `c_pwd` varchar(255) NOT NULL,
  `c_phone` varchar(255) DEFAULT NULL,
  `n_age` int(11) NOT NULL,
  `n_sex` int(11) NOT NULL,
  `c_createDate` varchar(255) DEFAULT NULL,
  `n_creater` bigint(20) DEFAULT NULL,
  `c_updateDate` varchar(255) DEFAULT NULL,
  `n_updater` bigint(20) DEFAULT NULL,
  `n_deleted` int(11) DEFAULT NULL,
  `n_status` int(11) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=91 DEFAULT CHARSET=utf8;

-- ----------------------------
-- Records of users
-- ----------------------------
INSERT INTO `users` VALUES ('8', 'admin', 'c4ca4238a0b92382', '1', '12', '0', '2017-08-01 11:00:05', '8', '2017-09-23 10:47:57', '8', '0', '1');

4、編寫ShiroConfig.java

package com.cy.example.config;

import java.util.LinkedHashMap;
import java.util.Map;

import javax.servlet.Filter;

import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.cy.example.filter.ShiroPermissionsFilter;
import com.cy.example.utils.AuthRealm;

/*
 * Shiro 配置
 */
@Configuration
public class ShiroConfig {

    private static final Logger logger = LoggerFactory
            .getLogger(ShiroConfig.class);

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

        Map<String, Filter> filters = shiroFilterFactoryBean.getFilters();//獲取filters  
        //將自定義 的ShiroFilterFactoryBean注入shiroFilter
        filters.put("perms", new ShiroPermissionsFilter());

        // 必須設定SecuritManager  
        shiroFilterFactoryBean.setSecurityManager(securityManager);
        // 攔截器.
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<String, String>();
        // 配置不會被攔截的連結 順序判斷
        filterChainDefinitionMap.put("/css/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/images/**", "anon");
        filterChainDefinitionMap.put("/js/**", "anon");
        filterChainDefinitionMap.put("/lib/**", "anon");

        filterChainDefinitionMap.put("/index", "anon");

    //這個是登入驗證的後臺地址,這裡把它過濾掉,讓自己的控制層來驗證
    filterChainDefinitionMap.put("/system/user/validate", "anon");
        // 配置退出 過濾器,其中的具體的退出程式碼Shiro已經替我們實現了
        filterChainDefinitionMap.put("/logout", "logout");
        // 這裡自定義的許可權攔截規則
        filterChainDefinitionMap.put("/system/*/add", "perms[add]");
        filterChainDefinitionMap.put("/system/*/delete", "perms[del]");
        // filterChainDefinitionMap.put("/system/*/list", "perms[list]");
        // <!-- authc:所有url都必須認證通過才可以訪問; anon:所有url都都可以匿名訪問-->
        filterChainDefinitionMap.put("/**", "authc");
        // 如果不設定預設會自動尋找Web工程根目錄下的"/login.jsp"頁面,這個就是類似於登入介面
        shiroFilterFactoryBean.setLoginUrl("/index");
        // 登入成功後要跳轉的連結
//      shiroFilterFactoryBean.setSuccessUrl("/main");

        // 未授權介面;
        // shiroFilterFactoryBean.setUnauthorizedUrl("/menu/403");
        shiroFilterFactoryBean
                .setFilterChainDefinitionMap(filterChainDefinitionMap);

        logger.info("--------------Shiro攔截器工廠類注入成功----------------");
        return shiroFilterFactoryBean;
    }

    /*
     * 配置自定義的許可權登入器
     */
    @Bean
    public AuthRealm authRealm() {
        AuthRealm authRealm = new AuthRealm();
//      authRealm.setCredentialsMatcher(matcher);
        return authRealm;
    }

    /*
     * 配置核心安全事務管理器
     */
    @Bean
    public SecurityManager securityManager() {
        logger.info("--------------shiro安全事務管理器已經載入----------------");
        DefaultWebSecurityManager manager = new DefaultWebSecurityManager();
        manager.setRealm(authRealm());
        return manager;
    }
}

5、建立realm,這個就是類似於用來賦值的。。我是這麼理解的,在這裡使用者和許可權的賦值。

package com.cy.example.utils;

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

import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.cy.example.entity.SysPermisEntity;
import com.cy.example.entity.SysRoleEntity;
import com.cy.example.entity.UserEntity;
import com.cy.example.service.UserService;

public class AuthRealm extends AuthorizingRealm {

    @Autowired
    private UserService userService;

    private static final Logger logger = LoggerFactory
            .getLogger(AuthRealm.class);

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(
            PrincipalCollection principals) {
        // TODO Auto-generated method stub
        logger.info("--------------許可權配置——授權----------------");
        SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
        UserEntity user = (UserEntity) principals.getPrimaryPrincipal();
        for (SysRoleEntity role : user.getRoleList()) {
            authorizationInfo.addRole(role.getC_roleName());
            for (SysPermisEntity p : role.getPermisList()) {
                authorizationInfo.addStringPermission(p.getC_permisName());
            }
        }
        logger.info(user.toString());
        return authorizationInfo;
    }

    /*
     * 認證資訊.(身份驗證) : Authentication 是用來驗證使用者身份 如果返回一個SimpleAccount
     * 物件則認證通過,如果返回值為空或者異常,則認證不通過。 1、檢查提交的進行認證的令牌資訊 2、根據令牌資訊從資料來源(通常為資料庫)中獲取使用者資訊
     * 3、對使用者資訊進行匹配驗證 4、驗證通過將返回一個封裝了使用者資訊的AuthenticationInfo例項
     * 5、驗證失敗則丟擲AuthenticationException異常資訊
     */
    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(
            AuthenticationToken token) throws AuthenticationException {
        // TODO Auto-generated method stub
        logger.info("***使用者身份驗證");
        // 獲取使用者的輸入的賬號.
        String username = (String) token.getPrincipal();
        if (StringUtil.IsNullOrEmptyT(username)) {
            return null;
        }
        logger.info("***" + token.getCredentials());
        // 實際專案中,這裡可以根據實際情況做快取,如果不做,Shiro自己也是有時間間隔機制,2分鐘內不會重複執行該方法
        UserEntity user = userService.selectOne(new EntityWrapper<UserEntity>().eq("c_username", username));

        logger.info("***登入user:" + user);
        SimpleAuthenticationInfo authenticationInfo = new SimpleAuthenticationInfo(
                user, // 使用者名稱
                user.getC_pwd(), // 密碼
                ByteSource.Util.bytes(user.getCredentialsSalt()),// 這裡的getCredentialsSalt()只是返回一個唯一值,我返回的是使用者名稱,用來加密的
                salt=username+salt
                getName() // realm name
        );
        return authenticationInfo;
    }

}

這裡的兩個方法就是用來給使用者和許可權賦值的。

6、給大家看一下我的資料庫查詢的sql

<select id="findOneByUsername" parameterType="java.lang.String" resultMap="BaseResultMap" >
       SELECT
            u.id,
            c_username,
            u.c_pwd,
            u.c_phone,
            u.n_age,
            u.n_status,
            u.c_createDate,
            u.n_creater,
            u.c_updateDate,
            u.n_updater,
            CASE
        WHEN n_sex = 1 THEN
            '男'
        WHEN n_sex = 0 THEN
            '女'
        END AS n_sex,
        r.c_roleName,
        r.id as r_id,
        p.id as p_id,
        p.c_permisName
        FROM
            users u
        LEFT JOIN sys_user_role ur ON u.id = ur.n_userId
        LEFT JOIN sys_roles r ON ur.n_roleId = r.id
        LEFT JOIN sys_role_permission rp ON rp.n_role_id = ur.n_roleId
        LEFT JOIN sys_permission p ON p.id = rp.n_permission_id
       WHERE u.c_username = #{c_username} and n_deleted=0
    </select>

通過左連線把角色和許可權查詢出來
7、看一下驗證登入的控制層

@SuppressWarnings("finally")
    @RequestMapping("/validate")
    @ResponseBody
    public Map<String, Object> validate(String username, String password) {
        Map<String, Object> map = new HashMap<String, Object>();
        password = MD5Util.GetMD5Code(password);
        UsernamePasswordToken usernamePasswordToken = new UsernamePasswordToken(
                username, password);
        boolean flag = true;
        String msg = "";
        Subject subject = SecurityUtils.getSubject();
        try {
            subject.login(usernamePasswordToken); // 完成登入
            UserEntity user = (UserEntity) subject.getPrincipal();
            subject.getSession().setAttribute(WebConfig.LOGIN_USER, user);
            LoginRecordEntity loginRecord = new LoginRecordEntity();
            loginRecord.setC_createDate(DateUtil.getNow());
            loginRecord.setC_loginIp(super.getIP(getRequest()));
            loginRecord.setC_username(user.getC_username());
            loginRecordService.add(loginRecord);
            msg = "登陸成功!";
            map.put("flag", flag);
        } catch (Exception exception) {
            if (exception instanceof UnknownAccountException) {
                logger.info("賬號不存在: -- > UnknownAccountException");
                msg = "登入失敗,使用者賬號不存在!";
            } else if (exception instanceof IncorrectCredentialsException) {
                logger.info(" 密碼不正確: -- >IncorrectCredentialsException");
                msg = "登入失敗,使用者密碼不正確!";
            } else {
                logger.info("else -- >" + exception);
                msg = "登入失敗,發生未知錯誤:" + exception;
            }
            map.put("flag", false);
        } finally {
            map.put("msg", msg);
            return map;
        }
    }

返回資料的格式看一下map就知道了。

8、最重要的一步,新增許可權驗證失敗的過濾器,當時搞這個許可權失敗JSON返回資料我搞了很久,走了不少彎路,希望大家能夠成功的整合shiro

package com.cy.example.filter;

import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

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

import org.apache.shiro.web.filter.authz.PermissionsAuthorizationFilter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.cy.example.utils.JsonUtil;
import com.cy.example.utils.StringUtil;

public class ShiroPermissionsFilter extends PermissionsAuthorizationFilter {

    private static final Logger logger = LoggerFactory
            .getLogger(ShiroPermissionsFilter.class);

    /**
     * shiro認證perms資源失敗後回撥方法
     * @param servletRequest
     * @param servletResponse
     * @return
     * @throws IOException
     */
    @Override
    protected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws IOException {
        logger.info("----------許可權控制-------------");
        HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
        HttpServletResponse httpServletResponse = (HttpServletResponse) servletResponse;
        String requestedWith = httpServletRequest.getHeader("X-Requested-With");
        if (!StringUtil.IsNullOrEmpty(requestedWith) &&
                StringUtil.IsEmpty(requestedWith, "XMLHttpRequest")) {//如果是ajax返回指定格式資料
            Map<String, Object> result = new HashMap<String, Object>();
            result.put("flag", false);
            result.put("msg", "許可權不足!");
            httpServletResponse.setCharacterEncoding("UTF-8");
            httpServletResponse.setContentType("application/json");
            httpServletResponse.getWriter().write(JsonUtil.collectToString(result));
        } else {//如果是普通請求進行重定向
            httpServletResponse.sendRedirect("/403");
        }
        return false;
    }

}

下面附上我的專案地址,想看原始碼 的可以去下載,對你有幫助請star我的github地址