/*
Navicat Premium Data Transfer
Source Server : localhost
Source Server Type : MySQL
Source Server Version : 50727
Source Host : localhost:3306
Source Schema : db_security_demo
Target Server Type : MySQL
Target Server Version : 50727
File Encoding : 65001
Date: 22/08/2022 16:50:39
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for tb_permission
-- ----------------------------
DROP TABLE IF EXISTS `tb_permission`;
CREATE TABLE `tb_permission` (
`permission_id` int(11) NOT NULL AUTO_INCREMENT,
`permission_name` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '权限名',
`deleted` int(2) NULL DEFAULT 0 COMMENT '删除状态0:未删除1:已删除',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
PRIMARY KEY (`permission_id`) USING BTREE,
INDEX `permission_name`(`permission_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 5 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_permission
-- ----------------------------
INSERT INTO `tb_permission` VALUES (1, 'select', 0, '2022-08-22 16:47:53', '2022-08-22 16:47:53');
INSERT INTO `tb_permission` VALUES (2, 'add', 0, '2022-08-22 16:48:02', '2022-08-22 16:48:02');
INSERT INTO `tb_permission` VALUES (3, 'delete', 0, '2022-08-22 16:48:12', '2022-08-22 16:48:12');
INSERT INTO `tb_permission` VALUES (4, 'update', 0, '2022-08-22 16:48:20', '2022-08-22 16:48:20');
-- ----------------------------
-- Table structure for tb_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_role`;
CREATE TABLE `tb_role` (
`role_id` int(11) NOT NULL AUTO_INCREMENT,
`role_name` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '角色名',
`deleted` int(2) NULL DEFAULT 0 COMMENT '删除状态0:未删除1:已删除',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
PRIMARY KEY (`role_id`) USING BTREE,
INDEX `role_name`(`role_name`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_role
-- ----------------------------
INSERT INTO `tb_role` VALUES (1, 'admin', 0, '2022-08-22 16:47:16', '2022-08-22 16:47:16');
INSERT INTO `tb_role` VALUES (2, 'user', 0, '2022-08-22 16:47:23', '2022-08-22 16:47:23');
-- ----------------------------
-- Table structure for tb_role_permission
-- ----------------------------
DROP TABLE IF EXISTS `tb_role_permission`;
CREATE TABLE `tb_role_permission` (
`role_permission_id` int(11) NOT NULL AUTO_INCREMENT,
`role_id` int(11) NOT NULL DEFAULT 0 COMMENT '角色ID',
`permission_id` int(11) NOT NULL DEFAULT 0 COMMENT '权限ID',
`deleted` int(2) NULL DEFAULT 0 COMMENT '删除状态0:未删除1:已删除',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
PRIMARY KEY (`role_permission_id`) USING BTREE,
INDEX `role_id`(`role_id`) USING BTREE,
INDEX `permission_id`(`permission_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '角色权限表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_role_permission
-- ----------------------------
INSERT INTO `tb_role_permission` VALUES (1, 1, 1, 0, '2022-08-22 16:48:55', '2022-08-22 16:48:55');
INSERT INTO `tb_role_permission` VALUES (2, 1, 2, 0, '2022-08-22 16:49:03', '2022-08-22 16:49:03');
INSERT INTO `tb_role_permission` VALUES (3, 1, 3, 0, '2022-08-22 16:49:11', '2022-08-22 16:49:11');
INSERT INTO `tb_role_permission` VALUES (4, 1, 4, 0, '2022-08-22 16:49:18', '2022-08-22 16:49:18');
INSERT INTO `tb_role_permission` VALUES (5, 1, 1, 0, '2022-08-22 16:49:32', '2022-08-22 16:49:32');
INSERT INTO `tb_role_permission` VALUES (6, 2, 4, 0, '2022-08-22 16:49:53', '2022-08-22 16:49:53');
-- ----------------------------
-- Table structure for tb_user
-- ----------------------------
DROP TABLE IF EXISTS `tb_user`;
CREATE TABLE `tb_user` (
`user_id` int(11) NOT NULL AUTO_INCREMENT,
`user_name` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '登录账号',
`user_pass` varchar(80) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '登录密码',
`user_mobile` varchar(20) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '手机号',
`user_sex` tinyint(1) NULL DEFAULT 0 COMMENT '性别,0未知1男2女',
`deleted` int(2) NULL DEFAULT 0 COMMENT '删除状态0:未删除1:已删除',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
PRIMARY KEY (`user_id`) USING BTREE,
INDEX `user_mobile`(`user_mobile`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_user
-- ----------------------------
INSERT INTO `tb_user` VALUES (1, 'admin', '$2a$10$VkXygAeexQvVVvFJrl86IusTls.xErakvSMf1rOYjKxDkNoYycvzK', '', 0, 0, '2022-08-22 16:45:45', '2022-08-22 16:45:45');
INSERT INTO `tb_user` VALUES (2, 'andy', '$2a$10$VkXygAeexQvVVvFJrl86IusTls.xErakvSMf1rOYjKxDkNoYycvzK', '', 0, 0, '2022-08-22 16:45:55', '2022-08-22 16:45:55');
-- ----------------------------
-- Table structure for tb_user_role
-- ----------------------------
DROP TABLE IF EXISTS `tb_user_role`;
CREATE TABLE `tb_user_role` (
`user_role_id` int(11) NOT NULL AUTO_INCREMENT,
`user_id` int(11) NOT NULL DEFAULT 0 COMMENT '用户ID',
`role_id` int(11) NOT NULL DEFAULT 0 COMMENT '角色ID',
`deleted` int(2) NULL DEFAULT 0 COMMENT '删除状态0:未删除1:已删除',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '更新时间',
PRIMARY KEY (`user_role_id`) USING BTREE,
INDEX `user_id`(`user_id`) USING BTREE,
INDEX `role_id`(`role_id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci COMMENT = '用户角色表' ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of tb_user_role
-- ----------------------------
INSERT INTO `tb_user_role` VALUES (1, 1, 1, 0, '2022-08-22 16:50:09', '2022-08-22 16:50:09');
INSERT INTO `tb_user_role` VALUES (2, 2, 2, 0, '2022-08-22 16:50:23', '2022-08-22 16:50:23');
SET FOREIGN_KEY_CHECKS = 1;
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
# 应用名称
spring.application.name=server-security-demo
# 应用服务 WEB 访问端口
server.port=9090
#下面这些内容是为了让MyBatis映射
#指定Mybatis的Mapper文件
mybatis.mapper-locations=classpath:mappers/*xml
#指定Mybatis的实体目录
mybatis.type-aliases-package=com.shenmazong.demo.pojo
# 数据库驱动:
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
# 数据源名称
spring.datasource.name=defaultDataSource
# 数据库连接地址
spring.datasource.url=jdbc:mysql://localhost:3306/db_security_demo?autoReconnect= true&useUnicode= true&characterEncoding=utf8&serverTimezone=Asia/Shanghai
# 数据库用户名&密码:
spring.datasource.username=root
spring.datasource.password=123456
package com.shenmazong.user.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.service.Contact;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
/**
* @author 军哥
* @version 1.0
* @description: Swagger文档配置类
* @date 2022/6/11 10:54
*/
@Configuration
@EnableSwagger2
public class Knife4jConfiguration {
@Bean(value = "defaultApi2")
public Docket defaultApi2() {
String groupName="1.0版本";
Docket docket=new Docket(DocumentationType.OAS_30)
.apiInfo(new ApiInfoBuilder()
.title("用户中心API")
.description("# 用户中心相关API的定义以及描述")
.termsOfServiceUrl("https://www.shenmazong.com")
.contact(new Contact("亮子说编程","https://www.shenmazong.com","3350996729@qq.com"))
.version("3.0")
.build())
//分组名称
.groupName(groupName)
.select()
//这里指定Controller扫描包路径
.apis(RequestHandlerSelectors.basePackage("com.shenmazong.user.controller"))
.paths(PathSelectors.any())
.build();
return docket;
}
}
一般情况下,我们如果需要自定义权限拦截,则需要涉及到FilterInvocationSecurityMetadataSource这个接口了。
这里有个坑爹的地方。如果用户未登录,但是已经设置了拦截白名单的URL,仍然会进入到权限验证里面来。起初,我以为不会进来,但后来跟踪源代码发现,还是会进来。只是此时的身份是一个匿名用户。其默认的实现为DefaultFilterInvocationSecurityMetadataSource。
spring security的认证和权限流程,大概就是有多个过滤器,一步步调用filter chain。它的身份认证其实是始于访问资源开始。如果一个用户已登录,那么访问受保护的资源,则会校验该用户是否有权限访问。如果没有权限,则会调用权限拒绝的处理器进行处理。如果有权限,则能顺利访问该资源;
一个用户未登录情况下,也即匿名用户,访问受保护的资源时,spring security会首先检查该资源是否需要权限,如果需要权限,然后再检查,该资源是否是白名单里面。如果是白名单,也能正常访问。如果是受保护的资源,则会提示该用户需要登录。
也即,当一个匿名用户,访问受保护的资源时,就会提示该用户需要登录。
package com.shenmazong.user.security;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.access.SecurityConfig;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import java.util.Collection;
/**
* @author 军哥
* @version 1.0
* @description: 文件访问过滤器
* @date 2022/8/29 16:54
*/
@Component
@Slf4j
public class MyFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
AntPathMatcher pathMatcher = new AntPathMatcher();
@Override
public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
// 用户请求地址
String requestUrl = ((FilterInvocation)o).getRequestUrl();
log.info("url =" + requestUrl);
// 白名单
// 放过swagger
if(pathMatcher.match("/doc.html", requestUrl)) {
return SecurityConfig.createList("ROLE_OK");
}
if(pathMatcher.match("/webjars/**", requestUrl)) {
return SecurityConfig.createList("ROLE_OK");
}
if(pathMatcher.match("/favicon.ico", requestUrl)) {
return SecurityConfig.createList("ROLE_OK");
}
if(pathMatcher.match("/swagger-resources", requestUrl)) {
return SecurityConfig.createList("ROLE_OK");
}
if(pathMatcher.match("/v3/**", requestUrl)) {
return SecurityConfig.createList("ROLE_OK");
}
// 放过登录、注册、验证码
if(pathMatcher.match("/user/userLogin", requestUrl)) {
return SecurityConfig.createList("ROLE_OK");
}
// 检查token
String token = ((FilterInvocation) o).getHttpRequest().getHeader("token");
if(token == null) {
log.info("ROLE_NO");
return SecurityConfig.createList("ROLE_NO");
}
// 解析token
log.info("ROLE_OK");
return SecurityConfig.createList("ROLE_OK");
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
AccessDecisionManager 顾名思义,访问决策管理器。做出最终的访问控制(授权)决定。
Makes a final access control (authorization) decision
package com.shenmazong.user.security;
import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import java.util.Collection;
/**
* @author 军哥
* @version 1.0
* @description: 角色和权限判断
* @date 2022/8/29 16:37
*/
@Component
public class MyAccessDecisionManager implements AccessDecisionManager {
@Override
public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
for (ConfigAttribute configAttribute : collection) {
// 没有登录
if(authentication instanceof AnonymousAuthenticationToken) {
}
else {
}
// 验证权限
if("ROLE_OK".equals(configAttribute.getAttribute())) {
// 允许访问
return;
}
}
// 抛出异常
throw new AccessDeniedException("非法请求");
// throw new UsernameNotFoundException("用户不存在");
}
@Override
public boolean supports(ConfigAttribute configAttribute) {
return true;
}
@Override
public boolean supports(Class<?> aClass) {
return true;
}
}
package com.shenmazong.user.security;
import com.alibaba.fastjson2.JSON;
import com.shenmazong.user.utils.ResultResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* @author 军哥
* @version 1.0
* @description: 自定义未登录返回信息的bean
* @date 2022/8/29 17:48
*/
@Component
public class MyAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
httpServletResponse.setContentType("application/json;charset=utf-8");
httpServletResponse.setStatus(401);
PrintWriter writer = httpServletResponse.getWriter();
ResultResponse failed = ResultResponse.FAILED(401, e.getMessage());
String json = JSON.toJSONString(failed);
writer.write(json);
writer.flush();
writer.close();
}
}
package com.shenmazong.user.security;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
/**
* @author 军哥
* @version 1.0
* @description: MySecurityConfig
* @date 2022/8/29 16:25
*/
@Configuration
@EnableWebSecurity
public class MySecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
MyFilterInvocationSecurityMetadataSource myFilterInvocationSecurityMetadataSource;
@Autowired
MyAccessDecisionManager myAccessDecisionManager;
@Autowired
MyAuthenticationEntryPoint myAuthenticationEntryPoint;
/**
* @description 访问控制
* @author 军哥
* @date 2022/8/29 16:30
* @version 1.0
*/
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
@Override
public <O extends FilterSecurityInterceptor> O postProcess(O o) {
// 设置访问过滤器
o.setSecurityMetadataSource(myFilterInvocationSecurityMetadataSource);
// 设置权限过滤器
o.setAccessDecisionManager(myAccessDecisionManager);
return o;
}
})
.and().formLogin().permitAll()
.and().csrf().disable();
// 设置自定义未登录错误处理
http.exceptionHandling().authenticationEntryPoint(myAuthenticationEntryPoint);
}
}
package com.shenmazong.user.controller;
import com.shenmazong.user.service.TbUserService;
import com.shenmazong.user.utils.ResultResponse;
import com.shenmazong.user.vo.IdVo;
import com.shenmazong.user.vo.LoginInfoVo;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author 军哥
* @version 1.0
* @description: IndexController
* @date 2022/8/29 16:10
*/
@RestController
@Slf4j
@RequestMapping(value = "/user")
@Api(tags = "用户登录测试")
public class IndexController {
@Autowired
TbUserService tbUserService;
@PostMapping(value = "/userLogin")
public ResultResponse userLogin(@RequestBody LoginInfoVo loginInfoVo) {
return tbUserService.userLogin(loginInfoVo);
}
@ApiOperation(value = "getUser")
@PostMapping(value = "/getUser")
public ResultResponse getUser(@RequestBody IdVo idVo) {
return tbUserService.getUser(idVo);
}
}
package com.shenmazong.user.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.shenmazong.user.entity.TbUser;
import com.shenmazong.user.service.TbUserService;
import com.shenmazong.user.mapper.TbUserMapper;
import com.shenmazong.user.utils.ResultResponse;
import com.shenmazong.user.utils.TokenUtils;
import com.shenmazong.user.vo.IdVo;
import com.shenmazong.user.vo.LoginInfoVo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.bcrypt.BCrypt;
import org.springframework.stereotype.Service;
/**
*
*/
@Service
@Slf4j
public class TbUserServiceImpl extends ServiceImpl<TbUserMapper, TbUser>
implements TbUserService{
@Override
public ResultResponse userLogin(LoginInfoVo loginInfoVo) {
//--1 检查用户是否存在
QueryWrapper<TbUser> wrapper = new QueryWrapper<>();
wrapper.lambda().eq(TbUser::getUserName, loginInfoVo.getUserName()).last("limit 1");
TbUser one = getOne(wrapper);
if(one == null) {
return ResultResponse.FAILED(404, "用户不存在");
}
//--2 验证密码
boolean checkpw = BCrypt.checkpw(loginInfoVo.getUserPass(), one.getUserPass());
if(!checkpw) {
return ResultResponse.FAILED(401, "密码不正确");
}
//--3 生成token
String token = TokenUtils.token()
.setKey("123456")
.setClaim("userId", "" + one.getUserId())
.setClaim("userName", one.getUserName())
.setClaim("userRole", "['admin','user']").makeToken();
log.info("token="+token);
one.setAccessToken(token);
return ResultResponse.SUCCESS(one);
}
@Override
public ResultResponse getUser(IdVo idVo) {
TbUser tbUser = getById(idVo.getId());
if(tbUser == null) {
return ResultResponse.FAILED(404, "用户不存在");
}
return ResultResponse.SUCCESS(tbUser);
}
}