Role-Based Access Control 基于角色的访问控制
Resource-Based Access Control 基于资源的访问控制
给项目添加认证与授权功能的框架, 就叫安全框架
认证: 验证用户身份的合法性(验证用户名和密码是否正确)
授权: 授权用户访问资源的权限
Spring Security
Shiro
sa-token
认证(Authentication), 就相当于验证账号和密码是否正确. 就是判断你的身份是否合法.
授权(Authorization), 就是授予你访问资源的权利
session会话管理,
加密(Cryptography), 一般就是给密码加密
maven的中央仓库: https://mvnrepository.com/
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-spring-boot-web-starterartifactId>
<version>1.9.1version>
dependency>
shiro自带6种加密方式分别是
MD2, MD5, SHA-1, SHA-256, SHA-384, SHA-512
我们选择SHA-256进行一次加密,试试
RandomNumberGenerator random = new SecureRandomNumberGenerator();
String salt = random.nextBytes().toHex();
System.out.println("salt = " + salt);
//SimpleHash构造函数的参数说明(加密方式, 原密码, 盐, 哈希迭代数)
String pass = new SimpleHash("SHA-256", "zhaogang", salt, 1).toHex();
System.out.println("pass = " + pass);
打印结果
salt = 17228b8287f254609b0e00471ddaee37
pass = 81bffda6895fa3eb78523412fb6ad659b62cd0a4bc4f990b6edcb7e386704842
我们把数据库里的密码和盐进行修改
Realm(域): 用来编写认证与授权代码的一个地方
public class MyRealm extends AuthorizingRealm {
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
return null;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null;
}
}
Authentication: 认证 [ɔːˌθentɪˈkeɪʃn]
Authorization: 授权 [ˌɔːθərəˈzeɪʃn]
@Configuration
public class ShiroConfig {
@Bean("authorizer")
public MyRealm myRealm(){
MyRealm realm = new MyRealm();
//添加一个凭证匹配器,设置了加密方式为SHA-256, 哈希迭代数为1, 存储格式为Hex16进制
realm.setCredentialsMatcher(new HashedCredentialsMatcher("SHA-256"));
return realm;
}
}
启动项目后,shiro要进行认证, 结果发现未认证, shiro会重定向到未认证的请求/login.jsp
为什么未认证
因为自定义Realm里负责认证的代码还是空的呢, 啥也没写, 如下
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
return null;
}
下面是shiro认证过程的流程思想图
修改登录代码为如下写法
@RequestMapping("/login")
@ResponseBody
public Result login(String username, String password, HttpServletRequest request){
//获取当前用户
Subject subject = SecurityUtils.getSubject();
//创建账号和密码的令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//登录
try {
subject.login(token);
//查询用户信息
SysUser user = sysUserService.getUserByUsername(username);
//把用户信息存储到session里
request.getSession().setAttribute("user", user);
//修改登录次数和最后登录时间
sysUserService.updateLoginCount(user.getUid());
} catch (UnknownAccountException e) {
return Result.error("账号错误");
} catch (IncorrectCredentialsException e) {
return Result.error("密码错误");
} catch (AuthenticationException e) {
e.printStackTrace();
return Result.error("认证错误");
}
return Result.success();
}
自定义Realm里认证的代码编写如下
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken token1 = (UsernamePasswordToken)token;
//账号
String username = token1.getUsername();
//判断账号是否正确
SysUser user = sysUserMapper.getUserByUsername(username);
if (user == null) {
throw new UnknownAccountException();
}
//返回认证对象, 将判断密码是否正确的工作交给shiro里的凭证匹配器来完成
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
username,
user.getPwd(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
return info;
}
shiro给我们提供了一些异常, 供我们所使用
@RequestMapping("/logout")
@ResponseBody
public Result logout(HttpServletRequest request){
//shiro里的退出
Subject subject = SecurityUtils.getSubject();
if (subject.isAuthenticated()) {
subject.logout();
}
return Result.success();
}
给shiro设置拦截器
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
//anon 不拦截
definition.addPathDefinition("/", "anon");
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/logout", "anon");
//登录成功后跳转的页面
definition.addPathDefinition("/login-success", "anon");
definition.addPathDefinition("/static/**", "anon");
//网页上面的图标
definition.addPathDefinition("/favicon.ico", "anon");
definition.addPathDefinition("/error", "anon");
//其他请求都必须认证才能通过 authc 表示必须认证才行
definition.addPathDefinition("/**", "authc");
return definition;
}
shiro的认证不能通过后, shiro会进行一个重定向的请求 /login.jsp
我们有两种方法来处理这个请求
在application.properties里设置shiro的未认证请求路径
# 默认, 可以修改
shiro.loginUrl=/login.jsp
在WebMvcConfig类中设置页面的跳转
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//未认证的请求
registry.addViewController("/login.jsp").setViewName("noauthe");
}
也可以不要设计一个错误的提示页面, 未认证就调转到登录页即可
# 也可以直接设置为跳转到登录页
shiro.loginUrl=/
(1), shiro配置类
@Configuration
public class ShiroConfig {
/**
* 自定义Realm, 用来编写认证与授权代码的地方.
*/
@Bean
public Realm realm(){
MyRealm realm = new MyRealm();
//添加一个凭证匹配器,设置了加密方式为SHA-256, 哈希迭代数为1, 存储格式为Hex16进制
realm.setCredentialsMatcher(new HashedCredentialsMatcher("SHA-256"));
return realm;
}
/**
* 拦截器设置,设置哪些该拦截, 哪些不该拦截
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/", "anon");
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/logout", "anon");
//登录成功后跳转的页面
definition.addPathDefinition("/login-success", "anon");
definition.addPathDefinition("/static/**", "anon");
//网页上面的图标
definition.addPathDefinition("/favicon.ico", "anon");
definition.addPathDefinition("/error", "anon");
//其他请求都必须认证才能通过
definition.addPathDefinition("/**", "authc");
return definition;
}
}
(2), 自定义Realm里的代码
public class MyRealm extends AuthorizingRealm {
@Autowired
private SysUserMapper sysUserMapper;
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("授权");
return null;
}
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken token1 = (UsernamePasswordToken)token;
String username = token1.getUsername();
SysUser user = sysUserMapper.getUserByUsername(username);
if (user == null) {
throw new UnknownAccountException();
}
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
username,
user.getPwd(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
return info;
}
}
(3),登录代码
@RequestMapping("/login")
@ResponseBody
public Result login(String username, String password, HttpServletRequest request){
//获取当前的用户
Subject subject = SecurityUtils.getSubject();
//创建一个令牌
UsernamePasswordToken token = new UsernamePasswordToken(username, password);
//登录
try {
subject.login(token);
SysUser user = sysUserService.getUserByUsername(username);
//账号和密码都正确, 则把用户信息存储到会话作用域中
request.getSession().setAttribute("user", user);
//修改登录次数和最后登录时间
sysUserService.updateLoginCount(user.getUid());
} catch (UnknownAccountException e) {
return Result.error("账号或者密码错误!");
} catch (IncorrectCredentialsException e) {
return Result.error("账号或者密码错误!!");
} catch (AuthenticationException e) {
e.printStackTrace();
return Result.error("认证失败");
}
return Result.success();
}
先学一个注解@RequiresRoles(“”) 基于角色的授权
在自定义Realm里编写授权代码, 给用户一个固定的角色名称
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.addRole("p1");
return info;
}
在控制层的方法上, 加入注解@RequiresRoles(“p1”)来指定必须是p1角色才能访问此方法
@RequestMapping("/user/query")
@RequiresRoles("p1")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}
在转添加页面的方法上, 加入注解@RequiresRoles(“p2”),来指定必须是p2角色才能访问此方法
@RequiresRoles("p2")
@GetMapping("/user/addpage")
public String addpage(ModelMap modelMap){}
结果测试
访问query方法时, 没有问题, 但是访问addpage方法时,页面报了一个异常
则在shiro配置文件里加入下面的代码即可.
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAdapterRegistry(){
DefaultAdvisorAutoProxyCreator creator = new
DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
在授权里, 给用户设置两个权限
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
System.out.println("授权");
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
Set<String> set = new HashSet<>();
set.add("user:query");
set.add("user:addsave");
info.setStringPermissions(set);
return info;
}
控制层里,给方法添加对应的访问权限
@RequiresPermissions("user:query")
@RequestMapping("/user/query")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}
@RequiresPermissions("user:addpage")
@GetMapping("/user/addpage")
public String addpage(ModelMap modelMap){}
因为在授权里,没有给用户设置user:addpage的权限, 故此,访问addpage方法时也会报异常
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String userName = (String) principals.getPrimaryPrincipal();
//查询角色
Set<String> roles = sysUserMapper.getRoleByUserName(userName);
Set<String> permissions = sysUserMapper.getPermissionByUserName(userName);
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
info.setRoles(roles);
info.setStringPermissions(permissions);
return info;
}
根据用户查询角色的SQL
select role_name
from sys_role r
inner join sys_user_role ur on r.role_id=ur.role_id
inner join sys_user u on ur.user_id=u.uid
where u.account='zhangfei'
控制层里根据角色访问的写法为
@RequiresRoles("admin")
@RequestMapping("/user/query")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}
@RequiresRoles(value = {"admin", "accounting"}, logical = Logical.OR)
@RequestMapping("/user/query")
public String query(Integer pageNum, ModelMap modelMap, String account,String name){}
Logical.OR表示前面的两个角色是或者关系, 默认是并且的关系
@ControllerAdvice
public class MyExceptionResolve {
/**
* 抓取未授权异常,跳转到页面
*/
@ExceptionHandler(UnauthorizedException.class)
public String unauthorized(){
return "error/noautho";
}
}
@ControllerAdvice
public class MyExceptionResolve {
private final static String XHR = "XMLHttpRequest";
/**
* 抓取未授权异常,跳转到页面
*/
@ExceptionHandler(UnauthorizedException.class)
public String unauthorized(HttpServletRequest request, HttpServletResponse response){
String xrw = request.getHeader("X-Requested-With");
//判断是否是ajax请求.
if (XHR.equals(xrw)) {
Result r = Result.error("权限不足");
response.setContentType("html/text; charset=utf-8");
try {
response.getWriter().println(JSON.toJSONString(r));
return null;
} catch (IOException e) {
e.printStackTrace();
}
}
return "error/noautho";
}
}
//基于角色的授权
@RequiresRoles(value = {"admin", "system"},logical = Logical.OR)
@RequiresRoles("admin")
//基于权限字符的授权
@RequiresPermissions("user:query")
这些注解可以放在方法上, 也可以放在类上
放在方法上表示, 运行这个方法必须满足指定的角色或者权限
放在类(控制层)上, 表示这个类中的所有方法都必须满足指定的角色或者权限
注解式授权如果不满足角色或者权限,则抛异常UnauthorizedException
Subject subject = SecurityUtils.getSubject();
//1,判断用户是否拥有admin角色
if (!subject.hasRole("admin")) {
//内部转到权限不足提示页面
return "error/noautho";
}
//2,判断用户是否拥有user:add的权限
if (!subject.isPermitted("user:add")) {
return Result.error("权限不足");
}
//3,判断用户是否拥有user:add的权限, 如果没有这个权限,则抛一个异常UnauthorizedException
subject.checkPermission("user:add");
shiro里有一个默认过滤器DefaultFilter的枚举,里面有13个过滤器.
过滤器(拦截)名称 | 解释 |
---|---|
anon | 不拦截,不登录就可以访问 |
authc | 认证拦截 |
roles | 基于角色的拦截 |
perms | 基于权限的拦截 |
port | 基于的端口的拦截 |
ssl | 带加密的请求,SSL拦截器 |
user | 认证或者用了记住我功能的拦截 |
rest | 基于RestFul风格的拦截 |
authcBasic | Basic HTTP 身份验证拦截器 |
logout | 退出拦截器 |
noSessionCreation | 不创建会话拦截器 |
基于拦截器的授权写法
definition.addPathDefinition("/user/query", "roles[admin]");
如果没有权限, 我们可以设置一个未授权的请求路径,跳转到一个友好的提示页面
shiro.unauthorizedUrl=/noautho
缺点: 下面的写法表示必须同时具有两个角色, 如果只觉有期中一个,则被拦截.
definition.addPathDefinition("/user/query", "roles[admin,system]");
基于权限字符的拦截
definition.addPathDefinition("/user/query", "perms[user:query]");
能隐藏一些标签, 不让没有权限的人看到
(1), 添加一个shiro标签的方言依赖包()
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
(2), 在shiro配置文件里, 创建一个方言组件
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
(3),在HTML页面里, 首先给html标签添加一个shiro标签的名字空间(ns=name space)
<html lang="zh" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
(4),可以使用的shiro标签
标签名 | 解释 |
---|---|
shiro:hasRole=“admin” | 只有角色名称是admin的用户才可以看到对应的标签 |
shiro:lacksRole=“admin” | 与上面正好相反, 拥有admin角色的用户,看不到这个标签 |
shiro:hasPermission=“‘user:add’” | 拥有user:add权限的人, 能看到这个标签 |
shiro:lacksPermission=“‘user:add’” | 与上面相反, 拥有user:add 看不到这个标签 |
shiro:hasAllRoles=“developer, admin” | 验证用户是否满足前面所有角色 |
shiro:hasAnyRoles=“admin, vip, developer” | 验证用户是否属于前面任意一个角色 |
shiro:hasAllPermissions=“‘user:query, user:add’” | 验证用户是有满足前面所有权限 |
shiro:hasAnyPermissions=“‘user:query, user:add’” | 验证用户是否满足前面任意一个权限< |
shiro:authenticated=“” | 已认证通过的用户。不包含已记住的用户 |
shiro:user=“” | 认证通过或已记住的用户 |
shiro:guest=“” | 验证当前用户是否为“访客”,即未认证(包含未记住)的用户 |
shiro:notAuthenticated=“” | 未认证通过用户,与 authenticated 标签相对应。 与 guest 标签的区别是,该标签包含已记住用户 |
欢迎你: <shiro:principal/> 表示输出用户的身份(常用的身份就是账号)
shiro也提供了一个session
Session session = subject.getSession();
在shiro-spring-boot-web-starter里, shiro的session和HttpSession做了整合,
用httpSession存储的数据, 可以用shiro的session取值.
//存, 取, 删
Session session = subject.getSession();
session.setAttribute("user", user);
session.getAttribute("user");
session.removeAttribute("user");
shiro中缓存的重要性: 提高授权的性能
没有缓存之前的现象是, 授权的代码会执行无数次,而添加了缓存后, 授权的代码只执行了一次
shiro里提供了一个基于内存的缓存
注意导包
import org.apache.shiro.cache.CacheManager;
@Bean
public CacheManager cacheManager(){
return new MemoryConstrainedCacheManager();
}
这个缓存实际上是一个Map
有缓存的编程模型如下:
@Configuration
public class ShiroConfig {
@Bean
@ConditionalOnMissingBean
public DefaultAdvisorAutoProxyCreator defaultAdvisorAdapterRegistry(){
DefaultAdvisorAutoProxyCreator creator = new
DefaultAdvisorAutoProxyCreator();
creator.setProxyTargetClass(true);
return creator;
}
@Bean("authorizer")
public MyRealm myRealm(){
MyRealm realm = new MyRealm();
//添加一个凭证匹配器,设置了加密方式为SHA-256, 哈希迭代数为1, 存储格式为Hex16进制
realm.setCredentialsMatcher(new HashedCredentialsMatcher("SHA-256"));
return realm;
}
/**
* 过滤拦截设置
* @return
*/
@Bean
public ShiroFilterChainDefinition shiroFilterChainDefinition(){
DefaultShiroFilterChainDefinition definition = new DefaultShiroFilterChainDefinition();
definition.addPathDefinition("/", "anon");
definition.addPathDefinition("/login", "anon");
definition.addPathDefinition("/logout", "anon");
//登录成功后跳转的页面
definition.addPathDefinition("/login-success", "anon");
definition.addPathDefinition("/static/**", "anon");
//网页上面的图标
definition.addPathDefinition("/favicon.ico", "anon");
definition.addPathDefinition("/error", "anon");
definition.addPathDefinition("/unautho", "anon");
//其他请求都必须认证才能通过
definition.addPathDefinition("/**", "authc");
return definition;
}
@Bean
public ShiroDialect shiroDialect() {
return new ShiroDialect();
}
@Bean
public CacheManager cacheManager(){
return new MemoryConstrainedCacheManager();
}
}
pom文件里需要的依赖包
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-spring-boot-web-starterartifactId>
<version>1.9.1version>
dependency>
<dependency>
<groupId>com.github.theborakompanionigroupId>
<artifactId>thymeleaf-extras-shiroartifactId>
<version>2.0.0version>
dependency>
自定义Realm里的代码
public class MyRealm extends AuthorizingRealm {
@Resource
private SysUserMapper sysUserMapper;
/**
* 授权
* @param principals
* @return
*/
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
String username = (String) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
//查询用户真正的角色
Set<String> roles = sysUserMapper.getRoleNameByUserName(username);
info.setRoles(roles);
//查询用户的权限字符
Set<String > permission=sysUserMapper.getPermissionByUserName(username);
info.setStringPermissions(permission);
return info;
}
/**
* 认证
* @param token
* @return
* @throws AuthenticationException
*/
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
UsernamePasswordToken token1 = (UsernamePasswordToken)token;
String username = token1.getUsername();
//判断账号是否正确
SysUser user = sysUserMapper.getUserByUsername(username);
if (user == null) {
throw new UnknownAccountException();
}
//返回认证对象, 将判断密码是否正确的工作交给shiro里的凭证匹配器来完成
SimpleAuthenticationInfo info = new SimpleAuthenticationInfo(
username,
user.getPwd(),
ByteSource.Util.bytes(user.getSalt()),
getName()
);
return info;
}
}
SQL语句
<select id="getRoleNameByUserName" resultType="java.lang.String">
select role_name
from sys_role r
inner join sys_user_role ur on r.role_id=ur.role_id
inner join sys_user u on ur.user_id=u.uid
where u.account=#{username}
select>
<select id="getPermissionByUserName" resultType="java.lang.String">
select m.pstr
from sys_menu m
inner join sys_role_menu rm on m.mid=rm.mid
inner join sys_role r on rm.role_id=r.role_id
inner join sys_user_role ur on r.role_id=ur.role_id
inner join sys_user u on ur.user_id=u.uid
where u.account = #{username}
and m.pstr is not null
select>
换一个Ehcache缓存
我们需要把ehcache整合到spring里, 还需要整合到shiro里,所以需要两个依赖包
(1) 添加依赖包
<dependency>
<groupId>org.apache.shirogroupId>
<artifactId>shiro-ehcacheartifactId>
<version>1.9.1version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-cacheartifactId>
dependency>
(2), 添加一个ehcache.xml配置文件
timeToIdleSeconds="120" //空闲时间: 120秒内,只要有查询,则时间重置为120秒.
例子: 米饭需要2分钟就会凉了, 但是2分钟内只要加热一次, 则米饭又需要2分钟才能凉.
timeToLiveSeconds="120" //生存时间, 只有2分钟,时间一到就删除掉.
<ehcache>
<diskStore path="java.io.tmpdir/shiro-ehcache"/>
<defaultCache
maxElementsInMemory="10000"
eternal="false"
timeToIdleSeconds="120"
timeToLiveSeconds="120"
overflowToDisk="false"
diskPersistent="false"
diskExpiryThreadIntervalSeconds="120"
/>
<cache name="userCache"
maxElementsInMemory="2000"
eternal="false"
timeToIdleSeconds="60"
timeToLiveSeconds="0"
overflowToDisk="false"
>cache>
ehcache>
(3),在springboot的application.properties里加载ehcache的配置文件
spring.cache.ehcache.config=classpath:/ehcache.xml
import net.sf.ehcache.CacheManager;
import org.apache.shiro.cache.ehcache.EhCacheManager;
@Bean
public EhCacheManager ehCacheManager(CacheManager cacheManager){
EhCacheManager ehCacheManager = new EhCacheManager();
ehCacheManager.setCacheManager(cacheManager);
return ehCacheManager;
}
(1),开启缓存
@EnableCaching
(2),在springboot里怎么使用这个缓存呢?
第一步,注入缓存管理器, 注意导包 import org.springframework.cache.CacheManager;
@Autowired
private CacheManager cacheManager;
第二步, 按照缓存使用原则的编程模型,开始编写代码
queryWrapper.orderByDesc("uid");
//=====================开始=========================
Cache cache = cacheManager.getCache("userCache");
String key = "user-"+pageNum;
PageInfo<SysUser> pageInfo = cache.get(key, PageInfo.class);
if (pageInfo == null) {
PageHelper.startPage(pageNum, 10);
List<SysUser> list = sysUserService.list(queryWrapper);
pageInfo = new PageInfo<>(list);
cache.put(key, pageInfo);
}
//=====================结束============================
modelMap.put("pageInfo", pageInfo);
modelMap.put("account", account);
modelMap.put("name", name);
第三步, 缓存和数据库同步问题,
我们再进行添加, 修改,删除时, 应该清空缓存
@PostMapping("/user/addsave")
@ResponseBody
public Result addsave(SysUser user, Integer[] roleIds){
boolean bj = sysUserService.addsave(user, roleIds);
//======================开始========================
if (bj) {
Cache cache = cacheManager.getCache("userCache");
cache.clear();
}
//======================结束========================
return Result.out(bj, "添加失败", user);
}