学习SpringSecurity第二集

  • 一、密码加密
  • 启动类注入BCryptPasswordEncoder
  • 业务类
  • SpringSecurityConfig
  • 二、开启SpringSecurity注解
  • 三、更细粒度权限控制_授权
  • 数据库建表
  • 数据访问层
  • 业务层
  • 业务层实现类
  • 创建MyPermissionEvaluatorImpl实现PermissionEvaluator接口
  • 将自定义的MyPermissionEvaluatorImpl注入SpringSecurityConfig配置类
  • application.yml
  • Controller
  • home.html页面
  • 启动测试
  • Bug JPA数据库查询一对多返回是同一条数据


一、密码加密

启动类注入BCryptPasswordEncoder

在新增用户的时候,如果想要密码以加密的方式存储在数据库中,就用到SpringSecurity的加密类BCryptPasswordEncoder

@Bean
    public BCryptPasswordEncoder encoder(){
        return new BCryptPasswordEncoder();
    }

在启动类中注入Spring容器

spring boot security 敏感接口关闭 springboot security教程_lua

业务类

在业务类中将密码加密后存入

@Autowired
    private BCryptPasswordEncoder encoder;


  @Override
    public void addSysUser(SysUser sysUser) {
        sysUser.setPassword(encoder.encode(sysUser.getPassword()));
        sysUserRepository.save(sysUser);
    }

spring boot security 敏感接口关闭 springboot security教程_shiro_02


spring boot security 敏感接口关闭 springboot security教程_java_03

SpringSecurityConfig

如果用户密码以这种加密的方式存储的话在登陆的时候就需要修改SpringSecurityConfig配置类代码了

spring boot security 敏感接口关闭 springboot security教程_spring boot_04


密码不加密:

@Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new PasswordEncoder() {
            @Override
            public String encode(CharSequence charSequence) {
                return charSequence.toString();
            }
            @Override
            public boolean matches(CharSequence charSequence, String s) {
                return s.equals(charSequence.toString());
            }
        });
    }

密码加密:

/**
     * 如果密码加密,则使用这种方法
     * @param auth
     * @throws Exception
     */
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
    }

二、开启SpringSecurity注解

spring boot security 敏感接口关闭 springboot security教程_spring_05

需要在SpringSecurityConfig配置类上加上@EnableGlobalMethodSecurity(prePostEnabled = true)开启全局全局 Securtiy 注解。

然后在Controller中使用@PreAuthorize("hasRole('ROLE_ADMIN')")注解,即可代替SpringSecurityConfig配置类中.antMatchers("/admin").hasRole("ADMIN")

spring boot security 敏感接口关闭 springboot security教程_shiro_06

原来配置类:

spring boot security 敏感接口关闭 springboot security教程_lua_07

三、更细粒度权限控制_授权

数据库建表

需要创建两张表:权限表和角色权限中间表

CREATE TABLE `sys_permission` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `url` varchar(255) DEFAULT NULL,
  `permission` varchar(255) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8 COMMENT='权限表';
CREATE TABLE `sys_role_permission` (
  `role_id` int(11) NOT NULL,
  `permission_id` int(11) NOT NULL,
  PRIMARY KEY (`role_id`,`permission_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色与权限关联表';
# 权限表
INSERT INTO `tensquare_user`.`sys_permission`(`id`, `url`, `permission`) VALUES (1, '/admin', 'c');
INSERT INTO `tensquare_user`.`sys_permission`(`id`, `url`, `permission`) VALUES (2, '/admin', 'r');
INSERT INTO `tensquare_user`.`sys_permission`(`id`, `url`, `permission`) VALUES (3, '/admin', 'u');
INSERT INTO `tensquare_user`.`sys_permission`(`id`, `url`, `permission`) VALUES (4, '/admin', 'd');
INSERT INTO `tensquare_user`.`sys_permission`(`id`, `url`, `permission`) VALUES (5, '/user', 'r');



# 角色权限中间表
INSERT INTO `tensquare_user`.`sys_role_permission`(`role_id`, `permission_id`) VALUES (1, 1);
INSERT INTO `tensquare_user`.`sys_role_permission`(`role_id`, `permission_id`) VALUES (1, 3);
INSERT INTO `tensquare_user`.`sys_role_permission`(`role_id`, `permission_id`) VALUES (1, 3);
INSERT INTO `tensquare_user`.`sys_role_permission`(`role_id`, `permission_id`) VALUES (1, 4);
INSERT INTO `tensquare_user`.`sys_role_permission`(`role_id`, `permission_id`) VALUES (2, 5);

数据访问层

权限表:

public interface SysPermissionRepository extends JpaRepository<SysPermission,Integer> {
    SysPermission findSysPermissionById(Integer id);
}

角色权限中间表

public interface SysRolePermissionRepository extends JpaRepository<SysRolePermission, Integer> {
   List<SysRolePermission> findSysRolePermissionByRoleId(Integer roleId);
}

角色表

public interface SysRoleRepository extends JpaRepository<SysRole,Integer> {
    /**
     * 根据 角色id查询角色
     * @param id
     * @return
     */
    SysRole findSysRolesById (Integer id);

    /**
     * 根绝角色name 查询角色
     * @param name
     * @return
     */
    SysRole findSysRoleByName(String name);

}

业务层

Service

/**
     * 根据角色name查询 角色信息
     * @param roleName
     * @return
     */
    SysRole findRoleIdBuRoleName(String roleName);

    
    /**
     * 根绝角色ID查询权限ID
     * @param id
     * @return
     */
    List<SysRolePermission> findPermissionsIdByRoleId(Integer id);

    /**
     * 根据权限id查询权限
     * @param permissionId
     * @return
     */
    SysPermission findPermissionByPermessionId(Integer permissionId);

业务层实现类

/**
     * 根据角色Name查询角色信息   查询角色表
     * @param roleName
     * @return
     */
    @Override
    public SysRole findRoleIdBuRoleName(String roleName) {
        return sysRoleRepository.findSysRoleByName(roleName);
    }

    /**
     * 根据角色ID查询权限ID   一对多
     * @param id
     * @return
     */
    @Override
    public List<SysRolePermission> findPermissionsIdByRoleId(Integer id) {
        return sysRolePermissionRepository.List<SysRolePermission> findSysRolePermissionByRoleId(id);
    }

    /**
     * 根据权限ID查询权限
     * @param permissionId
     * @return
     */
    @Override
    public SysPermission findPermissionByPermessionId(Integer permissionId) {
        return sysPermissionRepository.findSysPermissionById(permissionId);
    }

创建MyPermissionEvaluatorImpl实现PermissionEvaluator接口

代码逻辑:

  1. 通过 Authentication 取出登录用户的所有 RoleName
  2. 通过RoleName获得RoleID(查询角色表)
  3. 根据RoleID查询对应的PermissionID(查询角色权限中间表,一对多关系)
  4. 根据PermissionID查询对应权限(查询权限表)
    遍历每一个 Permission,只要有一个 Permission 的 url 和传入的url相同,且该 Permission 中包含传入的权限,返回 true,
    如果遍历都结束,还没有找到,返回false。
/**
 * @author :LiuShihao
 * @date :Created in 2020/11/10 5:33 下午
 * @desc : 对接口方法的更细粒度的角色权限控制
 */
@Component
public class MyPermissionEvaluatorImpl implements PermissionEvaluator {
    @Autowired
    SysService sysService;


    /**
     * @param authentication 认证过的信息
     * @param targetUrl   要访问的目标路径
     * @param targetPermission   要访问的目标路径需要的权限
     * @return
     */

    @Override
    public boolean hasPermission(Authentication authentication, Object targetUrl, Object targetPermission) {
        System.out.println("目标 url : "+targetUrl);
        System.out.println("目标 权限 : "+targetPermission);
        // 获得loadUserByUsername()方法的结果
        // User是org.springframework.security.core.userdetails.User
        // 是Userdetails的实现类
        User user = (User)authentication.getPrincipal();
        // 获得loadUserByUsername()中注入的角色
        Collection<GrantedAuthority> authorities = user.getAuthorities();
        // 遍历登录用户中所有角色
        for (GrantedAuthority authority : authorities) {
            //1. 获得角色名称  RoleName
            String roleName = authority.getAuthority();
            //2. 根据角色名查出角色信息并获得角色信息  获得RoleId
            SysRole role = sysService.findRoleIdBuRoleName(roleName);
            //3. 再根据角色ID查询角色权限中间表 对应的权限ID   一对多关系:一个RoleID对应多个PermissionId  返回集合
            List<SysRolePermission> permissionsIdByRoleId = sysService.findPermissionsIdByRoleId(role.getId());
            for (SysRolePermission sysRolePermission : permissionsIdByRoleId) {
                //4. 再根据权限ID查询权限信息  是一对一关系   一个权限id只对应一个权限信息
                SysPermission sysPermission = sysService.findPermissionByPermessionId(sysRolePermission.getPermissionId());
                System.out.println("角色是:"+roleName+"--对应的权限是:"+sysPermission);
                if (targetUrl.equals(sysPermission.getUrl()) && targetPermission.equals(sysPermission.getPermission())){
                    System.out.println("权限校验成功");
                    return true;
                }else {
                    System.out.println("权限校验失败");
                }
            }
        }
        return false;
    }

    @Override
    public boolean hasPermission(Authentication authentication, Serializable serializable, String s, Object o) {
        return false;
    }
}

将自定义的MyPermissionEvaluatorImpl注入SpringSecurityConfig配置类

/**
     * 注入自定义PermissionEvaluator
     */
    @Bean
    public DefaultWebSecurityExpressionHandler webSecurityExpressionHandler(){
        DefaultWebSecurityExpressionHandler handler = new DefaultWebSecurityExpressionHandler();
        handler.setPermissionEvaluator(new MyPermissionEvaluatorImpl());
        return handler;
    }

application.yml

允许覆盖框架自带的那个Bean,不然启动报错

spring.main.allow-bean-definition-overriding=true


spring:
  # 允许覆盖SpringSecurity框架自带的PermissionEvaluator,不然启动报错
  main:
    allow-bean-definition-overriding: true

Controller

控制层中添加方法

/**
     * 访问/adminR 需要admin角色 并且需要c权限
     * @return
     */
    @GetMapping("/adminC")
    @ResponseBody
    @PreAuthorize("hasPermission('/admin','c')")
    public String printAdminC() {
        return "如果你看见这句话,说明你有ROLE_ADMIN角色且具有c权限";
    }
    /**
     * 访问/adminR 需要admin角色 并且需要r权限
     * @return
     */
    @GetMapping("/adminR")
    @ResponseBody
    @PreAuthorize("hasPermission('/admin','r')")
    public String printAdminR() {
        return "如果你看见这句话,说明你有ROLE_ADMIN角色且具有r权限";
    }
    /**
     * 访问/adminR 需要admin角色 并且需要r权限
     * @return
     */
    @GetMapping("/adminU")
    @ResponseBody
    @PreAuthorize("hasPermission('/admin','u')")
    public String printAdminU() {
        return "如果你看见这句话,说明你有ROLE_ADMIN角色且具有u权限";
    }
    /**
     * 访问/adminR 需要admin角色 并且需要r权限
     * @return
     */
    @GetMapping("/adminD")
    @ResponseBody
    @PreAuthorize("hasPermission('/admin','d')")
    public String printAdminD() {
        return "如果你看见这句话,说明你有ROLE_ADMIN角色且具有d权限";
    }

    /**
     * 访问/userR 需要user角色 并且需要r权限
     * @return
     */
    @GetMapping("/userR")
    @PreAuthorize("hasPermission('/user','r')")
    @ResponseBody
    public String printUser3() {
        return "如果你看见这句话,说明你有ROLE_USER角色且具有r权限";
    }

    @GetMapping("/user/C")
    @PreAuthorize("hasPermission('/user','c')")
    @ResponseBody
    public String printUser2() {
        return "如果你看见这句话,说明你有ROLE_USER角色且具有c权限";
    }

home.html页面

<!DOCTYPE html>
<html lang="zh-CN">
<!--<html lang="zh-CN" xmlns:th="http://www.thymeleaf.org">-->
<head>
    <meta charset="utf-8">
    <meta name="description" content="练习使用SpringSecurity">
    <meta name="author" content="LiuShihao">
    <title>HOME</title>
    <link href="/css/bootstrap.min.css" rel="stylesheet" type="text/css">
    <link href="/css/signin.css" rel="stylesheet" type="text/css">
</head>

<body>
<div class="container">
    <h1>登录成功</h1>
    <a href="/admin">检测ROLE_ADMIN角色</a><hr><br>
    <a href="/user">检测ROLE_USER角色</a><hr><br>
    <a href="/adminC">检测ROLE_ADMIN角色并且具有C权限</a><hr><br>
    <a href="/adminR">检测ROLE_ADMIN角色并且具有R权限</a><hr><br>
    <a href="/adminU">检测ROLE_ADMIN角色并且具有U权限</a><hr><br>
    <a href="/adminD">检测ROLE_ADMIN角色并且具有D权限</a><hr><br>
    <a href="/user/C">检测ROLE_USER角色并且具有C权限</a><hr><br>
    <a href="/userR">检测ROLE_USER角色并且具有R权限</a><hr><br>
    <button onclick="window.location.href='/logout'">退出登录</button>

</div>
</body>
</html>

启动测试

spring boot security 敏感接口关闭 springboot security教程_java_08


spring boot security 敏感接口关闭 springboot security教程_spring boot_09


spring boot security 敏感接口关闭 springboot security教程_lua_10


spring boot security 敏感接口关闭 springboot security教程_spring boot_11

spring boot security 敏感接口关闭 springboot security教程_lua_12


spring boot security 敏感接口关闭 springboot security教程_spring boot_13


但是如果访问user_r、user_c则会抛出异常

spring boot security 敏感接口关闭 springboot security教程_lua_14


spring boot security 敏感接口关闭 springboot security教程_shiro_15


spring boot security 敏感接口关闭 springboot security教程_spring_16

Bug JPA数据库查询一对多返回是同一条数据

spring boot security 敏感接口关闭 springboot security教程_lua_17


spring boot security 敏感接口关闭 springboot security教程_lua_18


但是代码运行后查出来的数据是同一条数据

spring boot security 敏感接口关闭 springboot security教程_java_19

原因:

是实体类中的@Id注解

spring boot security 敏感接口关闭 springboot security教程_lua_20


我把@Id注解放在了roleid上,但是在表中这个字段不是唯一的

spring boot security 敏感接口关闭 springboot security教程_spring_21


解决方法:

所以加一个字段用来做主键id

spring boot security 敏感接口关闭 springboot security教程_shiro_22