本文主要介绍Springboot+Shiro+Mybatis三者的整合
目录
AuthenticationHandlerInterceptor.java
1.项目结构
- springboot-shiro-mybatis 是父目录
- shiro-pojo 用来存放一些实体类
- shiro-dao 持久化层,数据访问层,’连接数据库
- shiro-service 服务层,写一些复杂的逻辑
- shiro-web web端,一些web配置,访问的路径
数据库表
/*
SQLyog Ultimate v12.09 (64 bit)
MySQL - 5.7.21 : Database - wjx
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`wjx` /*!40100 DEFAULT CHARACTER SET utf8 */;
USE `wjx`;
/*Table structure for table `SysAuth` */
DROP TABLE IF EXISTS `SysAuth`;
CREATE TABLE `SysAuth` (
`sysAuthId` int(20) NOT NULL AUTO_INCREMENT COMMENT '权限ID',
`sysAuthCode` varchar(20) DEFAULT NULL COMMENT '权限编号',
`sysAuthName` varchar(20) DEFAULT NULL COMMENT '权限名称',
`sysAuthUrl` varchar(100) DEFAULT NULL COMMENT '请求的url',
`sysAuthPermission` varchar(100) NOT NULL COMMENT '权限的名称',
`sysAuthAva` int(1) DEFAULT '1' COMMENT '权限是否生效,0:未生效,1:生效',
`sysAuthType` int(2) DEFAULT '2' COMMENT '权限类型,0:菜单,2:按钮',
`sysAuthDes` varchar(100) DEFAULT NULL COMMENT '权限描述',
PRIMARY KEY (`sysAuthId`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
/*Data for the table `SysAuth` */
insert into `SysAuth`(`sysAuthId`,`sysAuthCode`,`sysAuthName`,`sysAuthUrl`,`sysAuthPermission`,`sysAuthAva`,`sysAuthType`,`sysAuthDes`) values (3001,'0001','查询用户','queryUser','queryUser',1,2,NULL),(3002,'0002','修改用户','updateUser','updateUser',1,2,NULL);
/*Table structure for table `SysRole` */
DROP TABLE IF EXISTS `SysRole`;
CREATE TABLE `SysRole` (
`sysRoleId` int(2) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`sysRoleAva` int(2) NOT NULL DEFAULT '1' COMMENT '角色是否生效,0:未生效,1:生效',
`sysRoleName` varchar(50) NOT NULL COMMENT '角色名称',
`sysRoleDes` varchar(50) NOT NULL COMMENT '角色描述',
PRIMARY KEY (`sysRoleId`)
) ENGINE=InnoDB AUTO_INCREMENT=2003 DEFAULT CHARSET=utf8;
/*Data for the table `SysRole` */
insert into `SysRole`(`sysRoleId`,`sysRoleAva`,`sysRoleName`,`sysRoleDes`) values (2001,1,'admin','管理员'),(2002,1,'test','测试角色');
/*Table structure for table `SysRoleAuth` */
DROP TABLE IF EXISTS `SysRoleAuth`;
CREATE TABLE `SysRoleAuth` (
`sysRoleId` int(20) DEFAULT NULL COMMENT '角色ID',
`sysAuthId` int(20) DEFAULT NULL COMMENT '权限ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `SysRoleAuth` */
insert into `SysRoleAuth`(`sysRoleId`,`sysAuthId`) values (2001,3001),(2001,3002),(2002,3001);
/*Table structure for table `SysUser` */
DROP TABLE IF EXISTS `SysUser`;
CREATE TABLE `SysUser` (
`userId` int(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`userAccount` varchar(20) NOT NULL COMMENT '用户账户',
`userPassword` varchar(20) NOT NULL COMMENT '用户密码',
`userState` int(2) NOT NULL DEFAULT '2' COMMENT '用户状态,0:锁定,1:禁用,2:激活',
PRIMARY KEY (`userId`)
) ENGINE=InnoDB AUTO_INCREMENT=1004 DEFAULT CHARSET=utf8;
/*Data for the table `SysUser` */
insert into `SysUser`(`userId`,`userAccount`,`userPassword`,`userState`) values (1001,'wjx','123',2),(1002,'admin','123',2),(1003,'test','123',2);
/*Table structure for table `SysUserRole` */
DROP TABLE IF EXISTS `SysUserRole`;
CREATE TABLE `SysUserRole` (
`sysUserId` int(20) DEFAULT NULL COMMENT '用户ID',
`sysRoleId` int(20) DEFAULT NULL COMMENT '权限ID'
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
/*Data for the table `SysUserRole` */
insert into `SysUserRole`(`sysUserId`,`sysRoleId`) values (1001,2001),(1001,2002),(1002,2001),(1002,2002),(1003,2002);
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
2.整合mybatis
2.1新建父工程
具体过程就不赘述了,父工程就是一个pom
配置了Springboot的版本,java版本,打包编译规则,还有一些modules依赖
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.wjx</groupId>
<artifactId>springboot-shiro-mybatis</artifactId>
<packaging>pom</packaging>
<version>1.0-SNAPSHOT</version>
<modules>
<module>shiro-web</module>
<module>shiro-pojo</module>
<module>shiro-dao</module>
<module>shiro-service</module>
<module>shiro-util</module>
</modules>
<name>springboot-shiro-mybatis</name>
<url>http://www.example.com</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.1.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<!--指定使用maven打包-->
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.19.1</version>
<configuration>
<skipTests>true</skipTests> <!--默认关掉单元测试 -->
</configuration>
</plugin>
</plugins>
</build>
</project>
2.2 创建shiro-util模块
项目右键 New->Module,名称为shiro-util
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-shiro-mybatis</artifactId>
<groupId>com.wjx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shiro-util</artifactId>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
</project>
Response.java文件
package com.wjx.util;
import lombok.Data;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/21 14:59
*/
@Data
public class Response {
private final static String SUCCESS = "SUCCESS";
private final static String FAIL = "FAIL";
//返回代码
private String code;
//返回信息
private String message;
//返回数据
private Object data;
//是否成功
private boolean isSuccess;
/**
* 成功
*
* @param data
* @return
*/
public static Response success(Object data) {
Response response = new Response();
response.code = SUCCESS;
response.isSuccess = true;
response.data = data;
return response;
}
/**
* 失败
*
* @param code
* @param message
* @return
*/
public static Response fail(String code, String message) {
Response response = new Response();
response.code = code;
response.message = message;
response.isSuccess = false;
return response;
}
/**
* 失败
*
* @param message
* @return
*/
public static Response fail(String message) {
Response response = new Response();
response.code = FAIL;
response.message = message;
response.isSuccess = false;
return response;
}
}
2.3 创建shiro-pojo模块
同理创建shiro-pojo模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-shiro-mybatis</artifactId>
<groupId>com.wjx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shiro-pojo</artifactId>
<dependencies>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.54</version>
</dependency>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>shiro-util</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
实体类代码
@Data
public class SysAuth {
private Integer sysAuthId;
private String sysAuthCode; //权限编号
private String sysAuthName; //权限名称
private String sysAuthUrl; //权限请求的url 例如: user/login
private String sysAuthPermission; //权限的的名称例如 user:login
private Byte sysAuthAva; //权限是否有效
private Byte sysAuthType; //权限类型。菜单还是按钮
private String sysAuthDes; //权限描述
}
@Data
public class SysRole {
private Integer sysRoleId;
private Byte sysRoleAva; //角色是否生效
private String sysRoleDes;//角色描述
private String sysRoleName;//角色名称
}
@Data
public class SysUser {
private Integer userId;
private String userAccount;//用户账号
private String userPassword;//用户密码
private Integer userState;//用户状态
}
2.4 创建shiro-dao模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-shiro-mybatis</artifactId>
<groupId>com.wjx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shiro-dao</artifactId>
<dependencies>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>shiro-pojo</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
UserMapper.java是接口
package com.wjx.mapper;
import com.wjx.pojo.SysAuth;
import com.wjx.pojo.SysRole;
import com.wjx.pojo.SysUser;
import org.apache.ibatis.annotations.Mapper;
import java.util.List;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/10 15:51
*/
@Mapper
public interface UserMapper {
//查询用户集合
List<SysUser> queryUser();
//根据用户名称查询用户
SysUser queryUserByUserName(SysUser sysUser);
//根据用户名称查询角色
List<SysRole> queryRoleByUserName(SysUser sysUser);
//根据用户名称查询权限
List<SysAuth> queryAuthByUserName(SysUser sysUser);
}
config.xml mybatis的配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!-- mybatis的配置文件 -->
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<typeAliases>
<package name="com.wjx.pojo"/>
</typeAliases>
</configuration>
UserMapper.xml 查询数据库
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wjx.mapper.UserMapper">
<select id="queryUserByUserName" resultType="SysUser">
SELECT * FROM SysUser u WHERE u.`userAccount`= #{userAccount}
</select>
</mapper>
2.5 创建shiro-service模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-shiro-mybatis</artifactId>
<groupId>com.wjx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shiro-service</artifactId>
<dependencies>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>shiro-dao</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
UserService.java
package com.wjx.service;
import com.wjx.pojo.SysAuth;
import com.wjx.pojo.SysRole;
import com.wjx.pojo.SysUser;
import java.util.List;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/10 15:53
*/
public interface UserService {
//查询用户集合
List<SysUser> queryUser();
//根据用户名称查询用户
SysUser queryUserByUserName(SysUser sysUser);
//根据用户名称查询角色
List<SysRole> queryRoleByUserName(SysUser sysUser);
//根据用户名称查询权限
List<SysAuth> queryAuthByUserName(SysUser sysUser);
}
UserServiceImpl.java
package com.wjx.service.impl;
import com.wjx.mapper.UserMapper;
import com.wjx.pojo.SysAuth;
import com.wjx.pojo.SysRole;
import com.wjx.pojo.SysUser;
import com.wjx.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/10 15:55
*/
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Override
public List<SysUser> queryUser() {
return userMapper.queryUser();
}
@Override
public SysUser queryUserByUserName(SysUser sysUser) {
return userMapper.queryUserByUserName(sysUser);
}
@Override
public List<SysRole> queryRoleByUserName(SysUser sysUser) {
return userMapper.queryRoleByUserName(sysUser);
}
@Override
public List<SysAuth> queryAuthByUserName(SysUser sysUser) {
return userMapper.queryAuthByUserName(sysUser);
}
}
2.6 创建shiro-web模块
pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<artifactId>springboot-shiro-mybatis</artifactId>
<groupId>com.wjx</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>shiro-web</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.wjx</groupId>
<artifactId>shiro-service</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>
<!--打包-->
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<!-- 指定该Main Class为全局的唯一入口 -->
<mainClass>com.Application</mainClass>
<!--<layout>ZIP</layout>-->
</configuration>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
application.properties
#数据库地址
spring.datasource.url=jdbc:mysql://114.116.24.32:3306/wjx?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=wjx
spring.datasource.password=123
server.port=8082
#mybatis地址
mybatis.config-location=classpath:mybatis/config.xml
mybatis.mapper-locations=classpath:mybatis/mapper/*.xml
#打印sql日志
logging.level.com.wjx.mapper=debug
UserController.java
package com.wjx.web.user;
import com.wjx.pojo.SysUser;
import com.wjx.service.UserService;
import com.wjx.util.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/10 15:46
*/
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("queryUserByUserName")
public Response queryUserByUserName(String userName) {
Response response = null;
SysUser sysUser = new SysUser();
sysUser.setUserAccount(userName);
try {
SysUser user = userService.queryUserByUserName(sysUser);
response = Response.success(user);
} catch (Exception e) {
e.printStackTrace();
response = Response.fail(e.getMessage());
}
return response;
}
}
运行Springboot,测试
3.整合Shiro框架
参考 https://blog.youkuaiyun.com/qq_35618489/article/details/86607932
添加Shiro的pom依赖,添加到 shiro-web 模块的pom里面
<!--整合shiro权限框架-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>1.4.0</version>
</dependency>
<!--shiro缓存插件-->
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>1.2.2</version>
</dependency>
更新 shiro-dao模块的UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.wjx.mapper.UserMapper">
<!--根据用户查询是否存在用户-->
<select id="queryUserByUserName" resultType="SysUser">
SELECT * FROM SysUser u WHERE u.`userAccount`= #{userAccount} and userPassword = #{userPassword}
</select>
<!--根据用户查询该用户的角色-->
<select id="queryRoleByUserName" resultType="SysRole">
SELECT
r.*
FROM
SysUser u,
SysUserRole ur,
SysRole r
WHERE u.`userId` = ur.`sysUserId`
AND ur.`sysRoleId` = r.`sysRoleId`
AND u.`userAccount` = #{userAccount}
</select>
<!--根据用户查询该用户对应的权限-->
<select id="queryAuthByUserName" resultType="SysAuth">
SELECT DISTINCT
a.*
FROM
SysUser u,
SysUserRole ur,
SysRole r ,
SysRoleAuth ra,
SysAuth a
WHERE u.`userId` = ur.`sysUserId`
AND ur.`sysRoleId` = r.`sysRoleId`
AND ra.`sysRoleId` = r.`sysRoleId`
AND ra.`sysAuthId` = a.`sysAuthId`
AND u.`userAccount` = #{userAccount}
</select>
</mapper>
ShiroUserFilter.java:重写shiro的UserFilter,实现通过OPTIONS复杂请求
AuthenticationHandlerInterceptor.java: Shiro自定义拦截权限
PermissionAccess:自定义注解
PermissionEnum: 自定义权限
RoleEnum: 自定义角色
ShiroRealm:自定义Shiro的权限控制
ShiroConfig:Shiro的权限注入、拦截配置
WebMvcConfig:SpringMvc的Web配置类
ShiroController:测试类
主要代码
ShiroUserFilter.java
package com.wjx.config.shiro.filter;
import com.alibaba.fastjson.JSONObject;
import com.wjx.util.Response;
import org.apache.shiro.web.filter.authc.UserFilter;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.RequestMethod;
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.PrintWriter;
/**
* 重写shiro的UserFilter,实现通过OPTIONS请求
*
* @author wjx
*/
public class ShiroUserFilter extends UserFilter {
/**
* 在访问过来的时候检测是否为OPTIONS请求,如果是就直接返回true
*/
@Override
protected boolean preHandle(ServletRequest request, ServletResponse response) throws Exception {
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpServletRequest httpRequest = (HttpServletRequest) request;
if (httpRequest.getMethod().equals(RequestMethod.OPTIONS.name())) {
setHeader(httpRequest, httpResponse);
return true;
}
return super.preHandle(request, response);
}
/**
* 该方法会在验证失败后调用,这里由于是前后端分离,后台不控制页面跳转
* 因此重写改成传输JSON数据
*/
@Override
protected void saveRequestAndRedirectToLogin(ServletRequest request, ServletResponse response) throws IOException {
saveRequest(request);
setHeader((HttpServletRequest) request, (HttpServletResponse) response);
PrintWriter out = response.getWriter();
out.println(JSONObject.toJSONString(Response.fail("未登录")));
out.flush();
out.close();
}
/**
* 为response设置header,实现跨域
*/
public static void setHeader(HttpServletRequest request, HttpServletResponse response) {
//跨域的header设置
response.setHeader("Access-control-Allow-Origin", request.getHeader("Origin"));
response.setHeader("Access-Control-Allow-Methods", request.getMethod());
response.setHeader("Access-Control-Allow-Credentials", "true");
response.setHeader("Access-Control-Allow-Headers", request.getHeader("Access-Control-Request-Headers"));
//防止乱码,适用于传输JSON数据
response.setHeader("Content-Type", "application/json;charset=UTF-8");
response.setStatus(HttpStatus.OK.value());
}
}
AuthenticationHandlerInterceptor.java
package com.wjx.config.shiro.permiss;
import com.alibaba.fastjson.JSONObject;
import com.wjx.config.shiro.filter.ShiroUserFilter;
import com.wjx.util.Response;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.springframework.stereotype.Component;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
/**
* @Description: Shiro自定义拦截权限
* @Auther: wjx
* @Date: 2019/1/21 17:59
*/
@Component
public class AuthenticationHandlerInterceptor extends HandlerInterceptorAdapter {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//设置编码格式
response.setCharacterEncoding("utf-8");
response.setContentType("application/json");
//将handler转化为HandlerMethod,
if (!(handler instanceof HandlerMethod)) {
return true;
}
HandlerMethod handlerMethod = (HandlerMethod) handler;
//从方法处理器取出要调用的方法
Method method = handlerMethod.getMethod();
PermissionAccess annotation = method.getAnnotation(PermissionAccess.class);
if (annotation == null) {
return true;
}
//访问需要的权限
PermissionEnum[] permissions = annotation.permission();
//访问需要的角色
RoleEnum[] roles = annotation.roles();
try {
Subject subject = SecurityUtils.getSubject();
List<RoleEnum> roleFailList = new ArrayList<>();
for (RoleEnum roleEnum : roles) {
if (subject.hasRole(roleEnum.role)) { //shiro存在该角色
continue;
}
roleFailList.add(roleEnum);//不存在
}
//验证权限
List<PermissionEnum> permissionFailList = new ArrayList<>();
for (PermissionEnum permissionEnum : permissions) {
if (subject.isPermitted(permissionEnum.url)) { //shiro框架存在权限
continue;
}
permissionFailList.add(permissionEnum);//不存在
}
StringBuilder builder = new StringBuilder();
if (roleFailList.size() != 0) {
roleFailList.stream().forEach(roleEnum -> {
builder.append(roleEnum.role + ",");
});
builder.append("角色权限不够");
ShiroUserFilter.setHeader(request, response);
response.getWriter().write(JSONObject.toJSONString(Response.fail(builder.toString())));
return false;
}
if (permissionFailList.size() != 0) {
permissionFailList.stream().forEach(permissionEnum -> {
builder.append(permissionEnum.url + ",");
});
builder.append("权限不足");
ShiroUserFilter.setHeader(request, response);
response.getWriter().write(JSONObject.toJSONString(Response.fail(builder.toString())));
return false;
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
}
PermissionAccess.java
package com.wjx.config.shiro.permiss;
import java.lang.annotation.*;
/**
* @Description: 自定义注解
* @Auther: wjx
* @Date: 2019/1/21 17:54
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface PermissionAccess {
//权限
PermissionEnum[] permission() default {};
//角色
RoleEnum[] roles() default {};
}
PermissionEnum.java
package com.wjx.config.shiro.permiss;
/**
* @Description: 权限的枚举
* @Auther: wjx
* @Date: 2019/1/21 17:48
*/
public enum PermissionEnum {
QUERY_USER("queryUser"),
UPDATE_USER("updateUser");
public String url;
PermissionEnum(String url) {
this.url = url;
}
public static PermissionEnum of(String urls) {
for (PermissionEnum permissionEnum : PermissionEnum.values()) {
if (permissionEnum.equals(urls)) {
return permissionEnum;
}
}
return null;
}
}
RoleEnum.java
package com.wjx.config.shiro.permiss;
/**
* @Description: 角色的枚举
* @Auther: wjx
* @Date: 2019/1/21 17:48
*/
public enum RoleEnum {
ADMIN("admin"),
TEST("test"),
WB("wb");
public String role;
RoleEnum(String url) {
this.role = url;
}
public static RoleEnum of(String role) {
for (RoleEnum roleEnum : RoleEnum.values()) {
if (roleEnum.equals(role)) {
return roleEnum;
}
}
return null;
}
}
ShiroRealm.java
package com.wjx.config.shiro.realm;
import com.wjx.pojo.SysAuth;
import com.wjx.pojo.SysRole;
import com.wjx.pojo.SysUser;
import com.wjx.service.UserService;
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.springframework.beans.factory.annotation.Autowired;
import java.util.List;
/**
* @Description: 自定义Shiro的权限控制
* @Auther: wjx
* @Date: 2019/1/21 14:21
* <p>
* Shiro的错误类型
* authc:
* AuthencationException:
* AuthenticationException 异常是Shiro在登录认证过程中,认证失败需要抛出的异常。
* <p>
* AuthenticationException包含以下子类:
* <p>
* CredentitalsException 凭证异常
* <p>
* IncorrectCredentialsException 不正确的凭证
* <p>
* ExpiredCredentialsException 凭证过期
* <p>
* AccountException 账号异常
* <p>
* ConcurrentAccessException 并发访问异常(多个用户同时登录时抛出)
* <p>
* UnknownAccountException 未知的账号
* <p>
* ExcessiveAttemptsException 认证次数超过限制
* <p>
* DisabledAccountException 禁用的账号
* <p>
* LockedAccountException 账号被锁定
* <p>
* UnsupportedTokenException 使用了不支持的Token
* <p>
*
* <p>
* ###############################################################################################
* <p>
* <p>
* authz:
* AuthorizationException:
* 子类:
* <p>
* UnauthorizedException:抛出以指示请求的操作或对请求的资源的访问是不允许的。
* <p>
* UnanthenticatedException:当尚未完成成功认证时,尝试执行授权操作时引发异常。
* (授权只能在成功的认证之后执行,因为授权数据(角色、权限等)必须总是与已知的标识相关联。这样的已知身份只能在成功登录时获得。)
*/
public class ShiroRealm extends AuthorizingRealm {
/**
* 正常这里需要连接数据库去读取配置,
* 在写demo的时候这里先不这么做
*/
@Autowired
private UserService userService;
/**
* 配置权限,注入权限
*
* @param principalCollection
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
System.out.println("----------------权限配置----------------");
SimpleAuthorizationInfo authorizationInfo = new SimpleAuthorizationInfo();
try {
SysUser user = (SysUser) principalCollection.getPrimaryPrincipal();
List<SysRole> sysRoleList = userService.queryRoleByUserName(user);
/**
* 注入角色
*/
for (SysRole role : sysRoleList) {
authorizationInfo.addRole(role.getSysRoleName());
}
List<SysAuth> sysAuthList = userService.queryAuthByUserName(user);
/**
* 注入权限
*/
for (SysAuth auth : sysAuthList) {
authorizationInfo.addStringPermission(auth.getSysAuthPermission());
}
} catch (Exception e) {
e.printStackTrace();
}
return authorizationInfo;
}
/**
* 用户验证
*
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
//加这一步的目的是在Post请求的时候会先进认证,然后在到请求
if (token.getPrincipal() == null) {
return null;
}
//获取用户输入的账户
String userName = (String) token.getPrincipal();
//这里需注意。看别人的教程有人是这样写的String password = (String) token.getCredentials();
//项目运行的时候报错,发现密码不正确。后来进源码查看发现将密码注入后。Shiro会进行转义将字符串转换成字符数组。
//源码:this(username, password != null ? password.toCharArray() : null, false, null);
//不晓得是否是因为版本的原因,建议使用的时候下载源码进行查看
String password = new String((char[]) token.getCredentials());
SysUser sysUser = userService.queryUserByUserName(new SysUser(userName, password));
if (!sysUser.getUserAccount().equals(userName)) {
throw new UnknownAccountException(); //账户不存在
} else {
if (!password.equals(sysUser.getUserPassword())) {
throw new IncorrectCredentialsException();
}
if (0 == sysUser.getUserState()) {
throw new LockedAccountException();//账户被锁定
} else if (1 == sysUser.getUserState()) {
throw new DisabledAccountException();//账户被禁用
} else {
SimpleAuthenticationInfo authorizationInfo = new SimpleAuthenticationInfo(sysUser, sysUser.getUserPassword(), getName());
return authorizationInfo;
}
}
}
}
ShiroConfig.java
package com.wjx.config.shiro;
import com.wjx.config.shiro.filter.ShiroUserFilter;
import com.wjx.config.shiro.realm.ShiroRealm;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.servlet.Filter;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
/**
* @Description: Shiro的权限注入、拦截配置
* @Auther: wjx
* @Date: 2019/1/21 15:14
*/
@Configuration
public class ShiroConfig {
/**
* 将自己的验证方法加入到容器
*
* @return
*/
@Bean
public ShiroRealm shiroRealm() {
return new ShiroRealm();
}
/**
* 权限管理,配置主要是Realm的管理认证
*
* @return
*/
@Bean
public SecurityManager securityManager() {
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
return securityManager;
}
/**
* Filter工厂,配置过滤条件和跳转条件
*
* @param securityManager
* @return
*/
@Bean
public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
System.out.println("--------------------shiro filter-------------------");
ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
shiroFilterFactoryBean.setSecurityManager(securityManager);
//添加过滤器
Map<String, Filter> filterMap = new HashMap<>();
ShiroUserFilter myPassFilter = new ShiroUserFilter();
filterMap.put("authc", myPassFilter);
shiroFilterFactoryBean.setFilters(filterMap);
Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
//注意过滤器配置顺序 不能颠倒
//配置退出 过滤器,其中的具体的退出代码Shiro已经替我们实现了,登出后跳转配置的loginUrl
// 配置不会被拦截的链接 顺序判断
filterChainDefinitionMap.put("/static/**", "anon");
filterChainDefinitionMap.put("/favicon.ico", "anon");
//拦截其他所以接口
filterChainDefinitionMap.put("/**", "authc");
//配置shiro默认登录界面地址,前后端分离中登录界面跳转应由前端路由控制,后台仅返回json数据
shiroFilterFactoryBean.setLoginUrl("/login");
// 登录成功后要跳转的链接 自行处理。不用shiro进行跳转
// shiroFilterFactoryBean.setSuccessUrl("user/index");
//未授权界面;
//shiroFilterFactoryBean.setUnauthorizedUrl("/user/unauth");
shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);
return shiroFilterFactoryBean;
}
/**
* 加入注解的使用,不加入这个验证权限注解不生效
*/
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
return authorizationAttributeSourceAdvisor;
}
}
WebMvcConfig.java
package com.wjx.config;
import com.wjx.config.shiro.permiss.AuthenticationHandlerInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurationSupport;
/**
* @Description: SpringMvc的Web配置类
* @Auther: wjx
* @Date: 2019/1/21 16:39
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurationSupport {
private String[] allowedOrigins = {"http://localhost:9090", "http://localhost:9091", "http://localhost:9092"};
/**
* 解决前后端跨域
*
* @param registry
*/
@Override
protected void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**") //对所有的路径都进行拦截
.allowedOrigins(allowedOrigins) //绑定IP,允许来自这个IP地址的cookie和session携带过来
.allowedMethods("POST", "GET", "PUT", "OPTIONS", "DELETE") //允许的方法
.allowCredentials(true) //允许携带cookie
.maxAge(3600); //设置最大缓存
}
@Override
protected void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(new AuthenticationHandlerInterceptor());
}
}
ShiroController.java
package com.wjx.web.shiro;
import com.wjx.config.shiro.permiss.PermissionAccess;
import com.wjx.config.shiro.permiss.PermissionEnum;
import com.wjx.config.shiro.permiss.RoleEnum;
import com.wjx.pojo.SysUser;
import com.wjx.util.Response;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.authc.*;
import org.apache.shiro.subject.Subject;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
/**
* @Description:
* @Auther: wjx
* @Date: 2019/1/21 14:57
*/
@RestController
public class ShiroController {
@RequestMapping("/login")
public Object Login(HttpServletRequest request) {
System.out.println("*****************************************************");
SysUser user = new SysUser();
user.setUserAccount("wjx");
user.setUserPassword("123");
Response response = null;
Subject subject = SecurityUtils.getSubject();
//数据库的密码我进行了Md5加密。如果没有进行加密的无需这个
//user.setUserPassword();
UsernamePasswordToken token = new UsernamePasswordToken(user.getUserAccount(), user.getUserPassword());
try {
subject.login(token);
response = Response.success("登录成功");
} catch (UnknownAccountException e) {
response = Response.fail("用户名不存在");
} catch (IncorrectCredentialsException e) {
e.printStackTrace();
response = Response.fail("密码错误");
} catch (LockedAccountException e) {
response = Response.fail("用户没有被激活");
} catch (DisabledAccountException e) {
response = Response.fail("用户被禁用");
} catch (Exception e) {
e.printStackTrace();
}
return response;
}
/**
* GRT 请求
*
* @return
*/
@RequestMapping("queryUser")
@PermissionAccess(permission = PermissionEnum.UPDATE_USER, roles = RoleEnum.WB)
public Object queryUser() {
System.out.println("queryUser");
return Response.success("queryUser");
}
/**
* 复杂请求POST测试
*
* @return
*/
@RequestMapping(value = "/getPost", method = RequestMethod.POST)
public Response index(@RequestBody SysUser user) {
return Response.success(user);
}
}
测试截图
4.前端代码
function login() {
$.ajax({
url : "http://localhost:8082/login",
type : 'get',
dataType : "json",
xhrFields : {
withCredentials : true
},
crossDomain : true,
success : function(data) {
console.log(data);
},
error : function(error, textStatus, errorThrown) {
console.log("error", error);
console.log("状态码:", error.status);
console.log("textStatus:", textStatus);
console.log("errorThrown:", errorThrown);
}
});
}
function queryUser() {
$.ajax({
url : "http://localhost:8082/queryUser",
type : 'get',
dataType : "json",
xhrFields : {
withCredentials : true
},
crossDomain : true,
success : function(data) {
console.log(data);
},
error : function(error, textStatus, errorThrown) {
console.log("error", error);
console.log("状态码:", error.status);
console.log("textStatus:", textStatus);
console.log("errorThrown:", errorThrown);
}
});
}
function postTest() {
var user = {
"userAccount" : "wjx",
"userPassword" : "123"
};
var data = JSON.stringify(user);
$.ajax({
async : true,
type : "post",
url : "http://localhost:8082/getPost",
data : data,
dataType : "json",
contentType : "application/json; charset=utf-8",
crossDomain : true,
xhrFields : {
withCredentials : true
},
dataType : "json",
success : function(data) {
console.log(data);
},
error : function(data) {
console.log(data)
}
})
}