springboot整合spring security案例

1.spring security要想短时间内啃完对于我这种菜鸟而言是不可能的,所以只能一点一点的积累,先做个demo,这文篇讲得可以Sprngboot + Spring Security 实现前后端分离登录认证及权限控制
2.这里是springsecurity的中文参考手册https://www.springcloud.cc/spring-security-zhcn.html,如下为本次springboot整合springsecurity入门案例的github地址:https://github.com/baisul/springbootsecurity.git
3.涉及到的表有如下,用户表,角色表,用户角色关联表,权限表,角色权限关联表,菜单表,权限菜单关联表
在这里插入图片描述

4.建表语句,以及插入部分数据

create table user(
	id int(11) primary key auto_increment,
	username varchar(36) default null comment'用户名',
	password varchar(36) default null comment'密码',
	email varchar(36) default null comment'邮箱',
	sex int(11) default null comment'性别'
)

insert into user(username,password,email,sex) values('joker','e10adc3949ba59abbe56e057f20f883e','joker@163.com',1)
insert into user(username,password,email,sex) values('admin','202cb962ac59075b964b07152d234b70','admin@163.com',0)
insert into user(username,password,email,sex) values('neva','63a9f0ea7bb98050796b649e85481845','neva@163.com',1)

create table role(
	id int(11) primary key auto_increment,
	role_name varchar(36) default null comment'角色名称',
	role_desc varchar(36) default null comment'角色描述'
)

insert into role(role_name,role_desc) values('超级管理员','拥有所有权限')
insert into role(role_name,role_desc) values('普通人员','拥有查看的权限')
insert into role(role_name,role_desc) values('管理员','拥有增删改查权限')

create table user_role_ref(
	id int(11) primary key auto_increment,
	user_id int(11) default null comment'用户id',
	role_id int(11) default null comment'角色id'
)

insert into user_role_ref(user_id,role_id) values(1,2)
insert into user_role_ref(user_id,role_id) values(2,1)
insert into user_role_ref(user_id,role_id) values(3,3)

create table permission(
	id int(11) primary key auto_increment,
	code varchar(100) default null comment'权限code',
	name varchar(100) default null comment'权限描述'
)

insert into permission(code,name) values('create_user','创建用户')
insert into permission(code,name) values('update_user','修改用户')
insert into permission(code,name) values('delete_user','删除用户')
insert into permission(code,name) values('query_user','查询用户')

create table role_permission_ref(
	id int(11) primary key auto_increment,
	role_id int(11) default null comment'角色id',
	permission_id int(11) default null comment'权限id'
)

insert into role_permission_ref(role_id,permission_id) values(1,1)
insert into role_permission_ref(role_id,permission_id) values(1,2)
insert into role_permission_ref(role_id,permission_id) values(1,3)
insert into role_permission_ref(role_id,permission_id) values(1,4)
insert into role_permission_ref(role_id,permission_id) values(3,1)
insert into role_permission_ref(role_id,permission_id) values(3,2)
insert into role_permission_ref(role_id,permission_id) values(3,3)
insert into role_permission_ref(role_id,permission_id) values(3,4)
insert into role_permission_ref(role_id,permission_id) values(2,4)

create table menu(
	id int(11) primary key auto_increment,
	url varchar(100) default null comment'路径地址',
	name varchar(30) default null comment'菜单名称',
	content varchar(100) default null comment'描述',
	state int(11) default null comment'是否可用,0可用,1禁用'
)

insert into menu(url,name,content,state) values('/user/getUsers','用户管理','用户列表',0)
insert into menu(url,name,content,state) values('/role/getRole','角色管理','角色列表',0)
insert into menu(url,name,content,state) values('/user/deleteUserById','用户管理','删除用户',0)

create table permission_menu_ref(
	id int(11) primary key auto_increment,
	permission_id int(11) default null comment'权限id',
	menu_id int(11) default null comment'菜单id'
)

insert into permission_menu_ref(permission_id,menu_id) values(4,1)
insert into permission_menu_ref(permission_id,menu_id) values(3,3)

5.pom.xml文件以及application.properties文件

<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.yl</groupId>
    <artifactId>springbootsecurity</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>springbootsecurity</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
<!--        <spring.security.version>5.1.6.RELEASE</spring.security.version>-->
        <fastjson.version>1.2.46</fastjson.version>
        <druid.version>1.1.12</druid.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-security</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.3</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>${druid.version}</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>${fastjson.version}</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.8.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework.security</groupId>
            <artifactId>spring-security-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

# Spring boot application
spring.application.name=springboot-security
server.port=8090
server.servlet.context-path=/sbs
spring.datasource.driverClassName=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/spring_security?useunicode=true;&serverTimezone=GMT&characterEncoding=utf8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.initialSize=5
spring.datasource.minIdle=5
spring.datasource.maxActive=20
spring.datasource.maxWait=60000
spring.datasource.timeBetweenEvictionRunsMillis=60000
spring.datasource.minEvictableIdleTimeMillis=300000
spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

#mybatsi,xml扫描
mybatis.mapper-locations=classpath*:mapper/*.xml

6.项目目录结构
在这里插入图片描述
7.常量类(utils包)

package com.yl.springbootsecurity.utils;

/**
 * 存放常量
 */
public interface Constant {
    /**
     * 状态成功码
     */
    public static final int SUCCESS_CODE = 200;

    /**
     * 状态失败码
     */
    public static final int FAIL_CODE = 0;
    /**
     * 操作成功
     */
    public static final boolean OPERATE_SUCCESS = true;
    /**
     * 操作失败
     */
    public static final boolean OPERATE_ERROR = false;

    public static final String USER_NOT_LOGIN = "用户未登录,请先登录";
    public static final String USER_NOT_EXIT = "用户不存在";
    public static final String USER_PASSWORD_ERROR = "密码输入出错";
    public static final String COMMON_ERROR = "系统出错";
    public static final String ACCOUNT_PASS_TIME = "账号过期";
    public static final String ACCOUNT_LOCK = "账号锁定";
    public static final String ACCOUNT_NOT_USE = "账号不可用";
    public static final String USER_PASSWORD_PASS_TIME = "密码过期";
}

8.返回结果类(utils包)

package com.yl.springbootsecurity.utils;

import java.io.Serializable;

/**
 * 公用的结果返回
 */
public class ResultModel implements Serializable {

    /**
     * 状态码:200成功,0失败
     */
    private Integer code;
    /**
     * 返回信息
     */
    private String msg;
    /**
     * 操作是否成功标志
     */
    private Boolean flag;
    /**
     * 返回数据
     */
    private Object data;


    public ResultModel(Integer code) {
        this.code = code;
    }

    public ResultModel(Integer code, String msg) {
        this.code = code;
        this.msg = msg;
    }

    public ResultModel(Integer code, String msg, Boolean flag) {
        this.code = code;
        this.msg = msg;
        this.flag = flag;
    }

    public ResultModel(Integer code, String msg, Object data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }

    public ResultModel(Integer code, String msg, Boolean flag, Object data) {
        this.code = code;
        this.msg = msg;
        this.flag = flag;
        this.data = data;
    }

    public Integer getCode() {
        return code;
    }

    public void setCode(Integer code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public Boolean getFlag() {
        return flag;
    }

    public void setFlag(Boolean flag) {
        this.flag = flag;
    }

    @Override
    public String toString() {
        return "ResultModel{" +
                "code=" + code +
                ", msg='" + msg + '\'' +
                ", flag=" + flag +
                ", data=" + data +
                '}';
    }
}


9.简单的md5util(utils包)

package com.yl.springbootsecurity.utils;

import org.springframework.util.DigestUtils;

/**
 * md5加密解密工具类
 */
public class Md5Util {

    //加密
    public static String encode(String str) {
        return DigestUtils.md5DigestAsHex(str.getBytes());
    }

    //匹配是否正确
    public static boolean isMatch(String ostr,String nstr) {
        if (encode(ostr).equals(nstr)) {
            return true;
        } else {
            return false;
        }
    }
}

10.实体类,user和permission(entity包),以下暂且都是先用到这两个实体类,来测试

package com.yl.springbootsecurity.entity;

import java.io.Serializable;

/**
 * 用户表实体类
 */
public class User implements Serializable {
    private Integer id;
    /**
     * 用户名
     */
    private String username;
    /**
     * 密码
     */
    private String password;
    /**
     * 邮箱
     */
    private String email;
    /**
     * 性别
     */
    private Integer sex;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    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;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public Integer getSex() {
        return sex;
    }

    public void setSex(Integer sex) {
        this.sex = sex;
    }
}

package com.yl.springbootsecurity.entity;

import java.io.Serializable;

/**
 * 权限表实体类
 */
public class Permission implements Serializable {
    private Integer id;
    /**
     * 权限编码
     */
    private String code;
    /**
     * 描述
     */
    private String name;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

11.mapper接口(mapper包)

package com.yl.springbootsecurity.mapper;

import com.yl.springbootsecurity.entity.User;

import java.util.List;

public interface UserMapper {

    User getUserByUserName(String username);

    List<User> getUsers();

    Integer deleteUserById(Integer id);
}

package com.yl.springbootsecurity.mapper;

import com.yl.springbootsecurity.entity.Permission;

import java.util.List;

public interface PermissionMapper {

    List<Permission> getPermissionsByUserId(Integer userId);

    List<Permission> selectListByPath(String path);
}


12.mapper映射文件(resources/mapper)

<?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.yl.springbootsecurity.mapper.UserMapper">

	<select id="getUserByUserName" parameterType="java.lang.String" resultType="com.yl.springbootsecurity.entity.User">
		select
			*
		from user
		where username = #{username}
	</select>

	<select id="getUsers" resultType="com.yl.springbootsecurity.entity.User">
		select * from user
	</select>

	<delete id="deleteUserById" parameterType="java.lang.Integer">
		delete from user where id = #{id}
	</delete>
</mapper>
<?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.yl.springbootsecurity.mapper.PermissionMapper">

	<select id="getPermissionsByUserId" parameterType="java.lang.Integer" resultType="com.yl.springbootsecurity.entity.Permission">
		select
			a.* from permission a
			left join role_permission_ref b on a.id = b.permission_id
			left join role c on b.role_id = c.id
			left join user_role_ref d on d.role_id = c.id
			left join user f on f.id = d.user_id
			where f.id = #{userId}
	</select>

	<select id="selectListByPath" parameterType="java.lang.String" resultType="com.yl.springbootsecurity.entity.Permission">
		select a. *
			from permission a
			left join permission_menu_ref b on a.id = b.permission_id
			left join menu c on b.menu_id = c.id
			where c.url = #{path}
	</select>
</mapper>

13.service接口(包service)

package com.yl.springbootsecurity.service;

import com.yl.springbootsecurity.entity.User;

import java.util.List;

public interface UserService {

    /**
     * 根据用户名获取用户
     * @param username
     * @return
     */
    User getUserByUserName(String username);

    /**
     * 获取用户列表信息
     * @return
     */
    List<User> getUsers();

    /**
     * 根据ID删除用户
     * @param id
     * @return
     */
    Integer deleteUserById(Integer id);
}

package com.yl.springbootsecurity.service;

import com.yl.springbootsecurity.entity.Permission;

import java.util.List;

public interface PermissionService {

    /**
     * 根据用户ID获取权限列表
     * @param userId
     * @return
     */
    List<Permission> getPermissionsByUserId(Integer userId);

    /**
     * 根据菜单路径去获取权限列表
     * @param path
     * @return
     */
    List<Permission> selectListByPath(String path);
}


14.service实现类(sevice.impl包)

package com.yl.springbootsecurity.service.impl;

import com.yl.springbootsecurity.entity.User;
import com.yl.springbootsecurity.mapper.UserMapper;
import com.yl.springbootsecurity.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * 用户表服务实现类
 */
@Service
public class UserServiceImpl implements UserService {
    //属性注入
    @Autowired
    private UserMapper userMapper;

    @Override
    public User getUserByUserName(String username) {
        return userMapper.getUserByUserName(username);
    }

    @Override
    public List<User> getUsers() {
        return userMapper.getUsers();
    }

    @Override
    public Integer deleteUserById(Integer id) {
        return userMapper.deleteUserById(id);
    }
}

package com.yl.springbootsecurity.service.impl;

import com.yl.springbootsecurity.entity.Permission;
import com.yl.springbootsecurity.mapper.PermissionMapper;
import com.yl.springbootsecurity.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class PermissionServiceImpl implements PermissionService {

    @Autowired
    private PermissionMapper permissionMapper;

    @Override
    public List<Permission> getPermissionsByUserId(Integer userId) {
        return permissionMapper.getPermissionsByUserId(userId);
    }

    @Override
    public List<Permission> selectListByPath(String path) {
        return permissionMapper.selectListByPath(path);
    }
}


15.controller层(controller包)

package com.yl.springbootsecurity.controller;

import com.yl.springbootsecurity.entity.User;
import com.yl.springbootsecurity.service.UserService;
import com.yl.springbootsecurity.utils.Constant;
import com.yl.springbootsecurity.utils.ResultModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * 用户表控制器
 */
@RestController
@RequestMapping("/user")
public class UserController {

    //属性注入
    @Autowired
    private UserService userService;

    /**
     * 获取用户列表
     * @return
     */
    @GetMapping("/getUsers")
    public ResultModel getUsers() {
        List<User> users = this.userService.getUsers();
        ResultModel resultModel;
        if (users.size() > 0) {
            resultModel = new ResultModel(Constant.SUCCESS_CODE,"查找成功",true,users);
        } else {
            resultModel = new ResultModel(Constant.SUCCESS_CODE,"暂无数据",true,null);
        }
        return resultModel;
    }

    /**
     * 根据用户ID删除用户
     * @param id
     * @return
     */
    @DeleteMapping("/deleteUserById")
    public ResultModel deleteUserById(@RequestParam(required = true) Integer id){
        ResultModel resultModel;
        Integer result = this.userService.deleteUserById(id);
        if (result > 0) {
            resultModel = new ResultModel(200,"删除成功");
        } else {
            resultModel = new ResultModel(0,"删除失败");
        }
        return resultModel;
    }
}

16.以上的常规操作做完后,接下来就是关于security的一些工作了
1)security的核心配置类(config包)


package com.yl.springbootsecurity.config;

import com.yl.springbootsecurity.filter.CustomizeAbstractSecurityInterceptor;
import com.yl.springbootsecurity.filter.CustomizeAccessDecisionManager;
//import com.yl.springbootsecurity.filter.CustomizeAuthenticationFilter;
import com.yl.springbootsecurity.filter.CustomizeFilterInvocationSecurityMetadataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * security核心配置类,继承WebSecurityConfigurerAdapter
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	//注入自定义认证提供者
	@Autowired
	private CustomizeAuthenticationProvider customizeAuthenticationProvider;

	//注入认证管理器
	@Override
	@Bean
	protected AuthenticationManager authenticationManager() throws Exception {
		return super.authenticationManager();
	}

	//注入登录成功的处理器
	@Bean
	public LoginSuccessHandler getLoginSuccessHandler() {
		return new LoginSuccessHandler();
	}

	//注入登录失败的处理器
	@Bean
	public LoginFailHandler getLoginFailHandler() {
		return new LoginFailHandler();
	}

	//注入未登录的异常处理器
	@Bean
	public CustomizeAuthenticationEntryPoint getCustomizeAuthenticationEntryPoint(){
		return new CustomizeAuthenticationEntryPoint();
	}

	//注入登出成功的处理器
	@Bean
	public CustomizeLogoutSuccessHandler getCustomizeLogOutSuccessHandler() {
		return new CustomizeLogoutSuccessHandler();
	}

	//注册自定义的UsernamePasswordAuthenticationFilter
//	@Bean
//	public CustomizeAuthenticationFilter getCustomizeAuthenticationFilter() throws Exception {
//		CustomizeAuthenticationFilter filter = new CustomizeAuthenticationFilter();
//		filter.setAuthenticationSuccessHandler(getLoginSuccessHandler());
//		filter.setAuthenticationFailureHandler(getLoginFailHandler());
		filter.setFilterProcessesUrl("/login/self");
//		filter.setFilterProcessesUrl("/authentication/form");
//
//		//这句很关键,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己组装AuthenticationManager
//		filter.setAuthenticationManager(authenticationManagerBean());
//		return filter;
//	}
	//访问决策管理器
	@Autowired
	CustomizeAccessDecisionManager accessDecisionManager;

	//实现权限拦截
	@Autowired
	CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;

	@Autowired
	private CustomizeAbstractSecurityInterceptor securityInterceptor;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//1.配置授权方式,这个configure方法里面主要是配置一些
		//http的相关配置,包括登入,登出,异常处理,会话管理等
		http.authorizeRequests().
				withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
					@Override
					public <O extends FilterSecurityInterceptor> O postProcess(O o) {
						o.setAccessDecisionManager(accessDecisionManager);//访问决策管理器
						o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
						return o;
					}
				})
				//登陆页面,登陆请求,登出请求,任何人都可以访问
				.antMatchers("/login","/authentication/form","/logout").permitAll()
				//动态加载权限时,这里注释掉
				.antMatchers("/user/getUsers").hasAuthority("query_user")
				.antMatchers("/user/deleteUserById").hasAuthority("delete_user")
				.anyRequest().authenticated()//其他所有请求都要认证
				//登入
				.and().formLogin()
				.permitAll() //允许所有人访问
				.loginProcessingUrl("/authentication/form") //登录成功后,处理登录的url
				.successHandler(getLoginSuccessHandler()) //登录成功后,调用成功处理器
				.failureHandler(getLoginFailHandler()) //登录失败,//调用失败处理器
				//登出
				.and().logout()
				.permitAll()
				.logoutSuccessUrl("/logout")
				.logoutSuccessHandler(getCustomizeLogOutSuccessHandler()) //登出成功后,调用登出成功处理器
				.deleteCookies("JSESSIONID")//登出之后删除cookie
				.and().headers()
				//异常处理(权限拒绝,登录失效)
				.and().exceptionHandling()
				//匿名用户访问无权限资源时的异常处理
				.authenticationEntryPoint(getCustomizeAuthenticationEntryPoint()); //异常处理器

		//用重写的Filter替换掉原有的UsernamePasswordAuthenticationFilter
//		http.addFilterAt(getCustomizeAuthenticationFilter(),
//				UsernamePasswordAuthenticationFilter.class);
		http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);//增加到默认拦截链中
		http.csrf().disable();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//2.配置认证方式
		//使用自定义的认证管理器
		auth.authenticationProvider(this.customizeAuthenticationProvider);
	}

}


2)自定义认证提供者类CustomizeAuthenticationProvider,这个类要实先Authentication接口,并且重写authentication这个方法,密码加密后比较还有根据用户获取权限列表可以在这个方法里面做

package com.yl.springbootsecurity.config;
import com.yl.springbootsecurity.entity.Permission;
import com.yl.springbootsecurity.service.PermissionService;
import com.yl.springbootsecurity.service.UserService;
import com.yl.springbootsecurity.utils.Md5Util;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.InternalAuthenticationServiceException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Service;

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


/**
 * 自定义认证提供者,对用户名和密码进行验证
 * @author Administrator
 */
@Service
public class CustomizeAuthenticationProvider implements AuthenticationProvider{

    @Autowired
    private UserService userService;

    @Autowired
    private PermissionService permissionService;

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        //获取前端传过来的的username和password
        String username = authentication.getName();
        String prepassword = (String)authentication.getCredentials();
        //定义UserDetails对象,构造再返回
        UserDetails userDetails = null;
        com.yl.springbootsecurity.entity.User user = this.userService.getUserByUserName(username);
        if (user == null) {
            throw new InternalAuthenticationServiceException("用户不存在");
        }
            //根据用户名获取权限列表
            List<Permission> permissions= permissionService.getPermissionsByUserId(user.getId());
            List<GrantedAuthority> authorities = new ArrayList<>();
            if (permissions != null && permissions.size() > 0) {
                permissions.stream().forEach(item -> {
                    GrantedAuthority grantedAuthority= new SimpleGrantedAuthority(item.getCode());
                    authorities.add(grantedAuthority);
                });
            }
            //构造UserDetails对象
            userDetails = new User(username, user.getPassword(), authorities);
            if (authentication.getCredentials() == null) {
                throw new BadCredentialsException("登录名或密码错误");
                //获取前端传过来的密码,经过加密之后和数据库保存的密码比较是否一致
            } else if (!Md5Util.encode(prepassword).equals(user.getPassword())) {
                throw new BadCredentialsException("密码错误");
            }
                //返回UsernamePasswordAuthenticationToken对象
                UsernamePasswordAuthenticationToken result = new UsernamePasswordAuthenticationToken(userDetails, authentication.getCredentials(), userDetails.getAuthorities());
                return result;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

3)登陆成功处理器,要实现AuthenticationSuccessHandler

package com.yl.springbootsecurity.config;


import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yl.springbootsecurity.utils.Constant;
import com.yl.springbootsecurity.utils.ResultModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 登陆成功处理器
 */
public class LoginSuccessHandler implements AuthenticationSuccessHandler {
	@Autowired
	private ObjectMapper objectMapper;

	@Override
	public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                                        Authentication authentication) throws IOException, ServletException {
		//处理中文乱码以及跨域问题
		response.setContentType("application/json;charset=UTF-8");
		response.setHeader("Access-Control-Allow-Origin", "*");
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");

		//得到用户
		User user = (User)authentication.getPrincipal();
		//这里可以做一些逻辑处理,比如,要返回这个用户的菜单权限到前端,前端拿到后,动态加载显示

		//返回数据
		ResultModel resultModel = new ResultModel(Constant.SUCCESS_CODE,"查找成功",Constant.OPERATE_SUCCESS,user);

		//以流的方式写数据到前端
		response.getWriter().write(JSON.toJSONString(resultModel));
	}

}


4)登录失败处理器,要实现AuthenticationFailureHandler

package com.yl.springbootsecurity.config;

import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yl.springbootsecurity.utils.Constant;
import com.yl.springbootsecurity.utils.ResultModel;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.*;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 *
 * 登陆失败处理器
 */
public class LoginFailHandler implements AuthenticationFailureHandler {

	@Override
	public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
                                        AuthenticationException e) throws IOException, ServletException {
		ResultModel resultModel;
		response.setContentType("application/json;charset=utf-8");
		response.setHeader("Access-Control-Allow-Origin", "*");
		response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");

		if (e instanceof InternalAuthenticationServiceException) {
			//用户不存在
			resultModel = new ResultModel(Constant.FAIL_CODE,Constant.USER_NOT_EXIT,Constant.OPERATE_ERROR);
		} else if (e instanceof BadCredentialsException) {
			//密码错误
			resultModel = new ResultModel(Constant.FAIL_CODE,Constant.USER_PASSWORD_ERROR,Constant.OPERATE_ERROR);
		} else if (e instanceof AccountExpiredException) {
			//账号过期
			resultModel = new ResultModel(Constant.FAIL_CODE,Constant.ACCOUNT_PASS_TIME,Constant.OPERATE_ERROR);
		} else if (e instanceof CredentialsExpiredException) {
			//密码过期
			resultModel = new ResultModel(Constant.FAIL_CODE,Constant.USER_PASSWORD_PASS_TIME,Constant.OPERATE_ERROR);
		} else if (e instanceof DisabledException) {
			//账号不可用
			resultModel = new ResultModel(Constant.FAIL_CODE,Constant.ACCOUNT_NOT_USE,Constant.OPERATE_ERROR);
		} else if (e instanceof LockedException) {
			//账号锁定
			resultModel = new ResultModel(Constant.FAIL_CODE,Constant.ACCOUNT_LOCK,Constant.OPERATE_ERROR);
		}else {
			resultModel = new ResultModel(Constant.FAIL_CODE,Constant.COMMON_ERROR,Constant.OPERATE_ERROR);
		}
		response.getWriter().write(JSON.toJSONString(resultModel));
	}

}

5)匿名用户访问时无权限的异常处理器,要实现AuthenticationEntryPoint

package com.yl.springbootsecurity.config;

import com.alibaba.fastjson.JSON;
import com.yl.springbootsecurity.utils.Constant;
import com.yl.springbootsecurity.utils.ResultModel;
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;

/**
 * 匿名用户访问无权限时的异常
 */

@Component
public class CustomizeAuthenticationEntryPoint implements AuthenticationEntryPoint {
    @Override
    public void commence(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, AuthenticationException e) throws IOException, ServletException {
        ResultModel resultModel = new ResultModel(Constant.FAIL_CODE,Constant.USER_NOT_LOGIN);
        httpServletResponse.setHeader("Access-Control-Allow-Origin", "*");
        httpServletResponse.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
        httpServletResponse.setContentType("application/json;charset=UTF-8");
        httpServletResponse.getWriter().write(JSON.toJSONString(resultModel));
    }
}

6)登出成功处理器,要实现LogoutSuccessHandler

package com.yl.springbootsecurity.config;

import com.alibaba.fastjson.JSON;
import com.yl.springbootsecurity.utils.Constant;
import com.yl.springbootsecurity.utils.ResultModel;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.web.authentication.logout.LogoutSuccessHandler;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

/**
 * 登出成功处理器
 */
public class CustomizeLogoutSuccessHandler implements LogoutSuccessHandler {
    @Override
    public void onLogoutSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
        //处理中文乱码及解决乱码
        response.setContentType("application/json;charset=utf-8");
        response.setHeader("Access-Control-Allow-Origin", "*");
        response.setHeader("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS, DELETE");
        //返回数据
        ResultModel resultModel = new ResultModel(Constant.SUCCESS_CODE,"登出成功",Constant.OPERATE_SUCCESS);
        //以流的方式写数据到前端
        response.getWriter().write(JSON.toJSONString(resultModel));
    }
}


17.测试一,这里先用postman来做测试
1)启动springboot主程序
在这里插入图片描述

2)访问删除用户接口,应该提示未登录,请先登录
在这里插入图片描述
3)在建表,插入数据的时候,我们加了三个用户,admin这个用户为超级管理员,拥有所有的权限,而joker这个用户只有查询的权限
4)以admin身份登录时,拥有所有的权限
在这里插入图片描述调查询接口可用
在这里插入图片描述

调删除接口可用

在这里插入图片描述

5)以joker身份登录时,只有查询的权限,这时候记得先登出,如下不再重复,只要是切换用户身份,第一步得先登出
在这里插入图片描述

在这里插入图片描述
调查询接口时可用
在这里插入图片描述

调删除接口时拒绝访问
在这里插入图片描述
18.在上面,我们首先是根据用户名去拿到用户信息,然后根据用户ID去获取其拥有的权限列表,还在配置类的configure方法那里配置了哪一些角色才可以访问某一一些请求路径,如下图:
在这里插入图片描述
19.那往后,我们还要加一些权限控制的话还需要手动回来这里加,不够灵活,所以可以搞成动态的,主要思路是参考上述的文章,里面说到AccessDecisionManager,即访问决策管理器,我们要写一个类实现它,其主要是用来对我们访问资源时做判断,如果有权限就放行,没权限就拒绝访问,在这之前,还要拦截到用户发起的请求,根据前端传来的url,去数据库查询当前url对应的权限列表,然后再交给访问决策器,这里涉及到另外一个东西,SecurityMetadataSource即安全元数据源,我们用它的一个子类FilterInvocationSecurityMetadataSource来完成上述操作,以上两个完成后,还有编写一个拦截器,增加到springsecurity的拦截器链中,最后还要在security的核心配置类,配置。

webSecurityConfig类最终如下


package com.yl.springbootsecurity.config;

import com.yl.springbootsecurity.filter.CustomizeAbstractSecurityInterceptor;
import com.yl.springbootsecurity.filter.CustomizeAccessDecisionManager;
//import com.yl.springbootsecurity.filter.CustomizeAuthenticationFilter;
import com.yl.springbootsecurity.filter.CustomizeFilterInvocationSecurityMetadataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

/**
 * security核心配置类,继承WebSecurityConfigurerAdapter
 */
@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

	//注入自定义认证提供者
	@Autowired
	private CustomizeAuthenticationProvider customizeAuthenticationProvider;

	//注入认证管理器
	@Override
	@Bean
	protected AuthenticationManager authenticationManager() throws Exception {
		return super.authenticationManager();
	}

	//注入登录成功的处理器
	@Bean
	public LoginSuccessHandler getLoginSuccessHandler() {
		return new LoginSuccessHandler();
	}

	//注入登录失败的处理器
	@Bean
	public LoginFailHandler getLoginFailHandler() {
		return new LoginFailHandler();
	}

	//注入未登录的异常处理器
	@Bean
	public CustomizeAuthenticationEntryPoint getCustomizeAuthenticationEntryPoint(){
		return new CustomizeAuthenticationEntryPoint();
	}

	//注入登出成功的处理器
	@Bean
	public CustomizeLogoutSuccessHandler getCustomizeLogOutSuccessHandler() {
		return new CustomizeLogoutSuccessHandler();
	}

	//注册自定义的UsernamePasswordAuthenticationFilter
//	@Bean
//	public CustomizeAuthenticationFilter getCustomizeAuthenticationFilter() throws Exception {
//		CustomizeAuthenticationFilter filter = new CustomizeAuthenticationFilter();
//		filter.setAuthenticationSuccessHandler(getLoginSuccessHandler());
//		filter.setAuthenticationFailureHandler(getLoginFailHandler());
		filter.setFilterProcessesUrl("/login/self");
//		filter.setFilterProcessesUrl("/authentication/form");
//
//		//这句很关键,重用WebSecurityConfigurerAdapter配置的AuthenticationManager,不然要自己组装AuthenticationManager
//		filter.setAuthenticationManager(authenticationManagerBean());
//		return filter;
//	}
	//访问决策管理器
	@Autowired
	CustomizeAccessDecisionManager accessDecisionManager;

	//实现权限拦截
	@Autowired
	CustomizeFilterInvocationSecurityMetadataSource securityMetadataSource;

	@Autowired
	private CustomizeAbstractSecurityInterceptor securityInterceptor;

	@Override
	protected void configure(HttpSecurity http) throws Exception {
		//1.配置授权方式,这个configure方法里面主要是配置一些
		//http的相关配置,包括登入,登出,异常处理,会话管理等
		http.authorizeRequests().
				withObjectPostProcessor(new ObjectPostProcessor<FilterSecurityInterceptor>() {
					@Override
					public <O extends FilterSecurityInterceptor> O postProcess(O o) {
						o.setAccessDecisionManager(accessDecisionManager);//访问决策管理器
						o.setSecurityMetadataSource(securityMetadataSource);//安全元数据源
						return o;
					}
				})
				//登陆页面,登陆请求,登出请求,任何人都可以访问
				.antMatchers("*/login","/authentication/form","/logout","*/home").permitAll()
				//动态加载权限时,这里注释掉
//				.antMatchers("/user/getUsers").hasAuthority("query_user")
//				.antMatchers("/user/deleteUserById").hasAuthority("delete_user")
				.anyRequest().authenticated()//其他所有请求都要认证
				//登入
				.and().formLogin()
				.permitAll() //允许所有人访问
				.loginProcessingUrl("/authentication/form") //登录成功后,处理登录的url
				.successHandler(getLoginSuccessHandler()) //登录成功后,调用成功处理器
				.failureHandler(getLoginFailHandler()) //登录失败,//调用失败处理器
				//登出
				.and().logout()
				.permitAll()
				.logoutSuccessUrl("/logout")
				.logoutSuccessHandler(getCustomizeLogOutSuccessHandler()) //登出成功后,调用登出成功处理器
				.deleteCookies("JSESSIONID")//登出之后删除cookie
				.and().headers()
				//异常处理(权限拒绝,登录失效)
				.and().exceptionHandling()
				//匿名用户访问无权限资源时的异常处理
				.authenticationEntryPoint(getCustomizeAuthenticationEntryPoint()); //异常处理器

		//用重写的Filter替换掉原有的UsernamePasswordAuthenticationFilter
//		http.addFilterAt(getCustomizeAuthenticationFilter(),
//				UsernamePasswordAuthenticationFilter.class);
		http.addFilterBefore(securityInterceptor, FilterSecurityInterceptor.class);//增加到默认拦截链中
		http.csrf().disable();
	}

	@Override
	protected void configure(AuthenticationManagerBuilder auth) throws Exception {
		//2.配置认证方式
		//使用自定义的认证管理器
		auth.authenticationProvider(this.customizeAuthenticationProvider);
	}

}


20.权限拦截器

package com.yl.springbootsecurity.filter;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.SecurityMetadataSource;
import org.springframework.security.access.intercept.AbstractSecurityInterceptor;
import org.springframework.security.access.intercept.InterceptorStatusToken;
import org.springframework.security.web.FilterInvocation;
import org.springframework.security.web.access.intercept.FilterInvocationSecurityMetadataSource;
import org.springframework.stereotype.Service;

import javax.servlet.*;
import java.io.IOException;

/**
 * 权限拦截器
 */

@Service
public class CustomizeAbstractSecurityInterceptor extends AbstractSecurityInterceptor implements Filter {

    @Autowired
    private FilterInvocationSecurityMetadataSource securityMetadataSource;

    @Autowired
    public void setMyAccessDecisionManager(CustomizeAccessDecisionManager accessDecisionManager) {
        super.setAccessDecisionManager(accessDecisionManager);
    }

    @Override
    public Class<?> getSecureObjectClass() {
        return FilterInvocation.class;
    }

    @Override
    public SecurityMetadataSource obtainSecurityMetadataSource() {
        return this.securityMetadataSource;
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        FilterInvocation fi = new FilterInvocation(servletRequest, servletResponse, filterChain);
        invoke(fi);
    }

    public void invoke(FilterInvocation fi) throws IOException, ServletException {
        //fi里面有一个被拦截的url
        //里面调用MyInvocationSecurityMetadataSource的getAttributes(Object object)这个方法获取fi对应的所有权限
        //再调用MyAccessDecisionManager的decide方法来校验用户的权限是否足够
        InterceptorStatusToken token = super.beforeInvocation(fi);
        try {
            //执行下一个拦截器
            fi.getChain().doFilter(fi.getRequest(), fi.getResponse());
        } finally {
            super.afterInvocation(token, null);
        }
    }
}



21.安全元数据源,CustomizeFilterInvocationSecurityMetadataSource


package com.yl.springbootsecurity.filter;

import com.yl.springbootsecurity.entity.Permission;
import com.yl.springbootsecurity.service.PermissionService;
import org.springframework.beans.factory.annotation.Autowired;
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;
import java.util.List;

/**
 * 安全元数据源FilterInvocationSecurityMetadataSource,主要是拦截到请求路径,做相关处理
 */
@Component
public class CustomizeFilterInvocationSecurityMetadataSource implements FilterInvocationSecurityMetadataSource {
    AntPathMatcher antPathMatcher = new AntPathMatcher();
    @Autowired
    private PermissionService permissionService;

    @Override
    public Collection<ConfigAttribute> getAttributes(Object o) throws IllegalArgumentException {
        //获取请求地址
        String requestUrl = ((FilterInvocation) o).getRequestUrl();
        List<Permission> permissionList;
        //根据请求地址获取该地址有哪一些权限才能够访问
        //不是get请求的情况,直接查数据
        if (!requestUrl.contains("?")) {
            permissionList =  permissionService.selectListByPath(requestUrl);
        } else {
            //为get请求的情况下,要切割字符串,因为?后面带的是一些参数,我们在数据库保存的仅仅是请求地址
            String [] arrs = requestUrl.split("\\?");
           permissionList =  permissionService.selectListByPath(arrs[0]);
        }
        if(permissionList == null || permissionList.size() == 0){
            //请求路径没有配置权限,表明该请求接口可以任意访问
            return null;
        }
        //如果当前请求地址需要权限才能够访问
        String[] attributes = new String[permissionList.size()];
        for(int i = 0;i<permissionList.size();i++){
            attributes[i] = permissionList.get(i).getCode();
        }
        //调用Securtity的createList方法,构造出一个List<ConfigAttribute>的集合
        //因为这个集合在访问决策管理器要用到
        return SecurityConfig.createList(attributes);
    }

    @Override
    public Collection<ConfigAttribute> getAllConfigAttributes() {
        return null;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}

22.访问决策管理器,CustomizeAccessDecisionManager

package com.yl.springbootsecurity.filter;

import org.springframework.security.access.AccessDecisionManager;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.ConfigAttribute;
import org.springframework.security.authentication.InsufficientAuthenticationException;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.stereotype.Component;

import java.util.Collection;
import java.util.Iterator;

/**
 *  访问决策管理器
 */
@Component
public class CustomizeAccessDecisionManager implements AccessDecisionManager {
    @Override
    public void decide(Authentication authentication, Object o, Collection<ConfigAttribute> collection) throws AccessDeniedException, InsufficientAuthenticationException {
                Iterator<ConfigAttribute> iterator = collection.iterator();
        while (iterator.hasNext()) {
            ConfigAttribute ca = iterator.next();
            //当前请求需要的权限
            String needRole = ca.getAttribute();
            //当前用户所具有的权限
            Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
           //如果当前用户拥有当前请求的权限则放行
            for (GrantedAuthority authority : authorities) {
                if (authority.getAuthority().equals(needRole)) {
                    return;
                }
            }
        }
        //没有权限则抛异常
        throw new AccessDeniedException("权限不足!");
    }

    @Override
    public boolean supports(ConfigAttribute configAttribute) {
        return true;
    }

    @Override
    public boolean supports(Class<?> aClass) {
        return true;
    }
}


23.测试二,动态权限访问的测试,这里测试还是用postman,只是把请求地址和授权角色写死的那一块注释掉,动态地来实现认证,授权
在这里插入图片描述

1)以admin身份登录
在这里插入图片描述

调用用户列表接口成功
在这里插入图片描述

调用用户删除接口成功
在这里插入图片描述

2)以joker身份登录,记得先登出
在这里插入图片描述

在这里插入图片描述

调用用户列表接口成功
在这里插入图片描述

调用用户删除接口,拒绝

在这里插入图片描述
24.至此。案例已完成,本想着结合vue来做测试的,但测试时有点问题,往后有时间再做吧

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值