Spring boot整合shiro使用Ajax方式,最詳細教程
阿新 • • 發佈:2019-02-03
最近一直在自己的個人專案中整合進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地址