学习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容器
业务类
在业务类中将密码加密后存入
@Autowired
private BCryptPasswordEncoder encoder;
@Override
public void addSysUser(SysUser sysUser) {
sysUser.setPassword(encoder.encode(sysUser.getPassword()));
sysUserRepository.save(sysUser);
}
SpringSecurityConfig
如果用户密码以这种加密的方式存储的话在登陆的时候就需要修改SpringSecurityConfig配置类代码了
密码不加密:
@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注解
需要在SpringSecurityConfig配置类上加上@EnableGlobalMethodSecurity(prePostEnabled = true)
开启全局全局 Securtiy 注解。
然后在Controller中使用@PreAuthorize("hasRole('ROLE_ADMIN')")
注解,即可代替SpringSecurityConfig配置类中.antMatchers("/admin").hasRole("ADMIN")
原来配置类:
三、更细粒度权限控制_授权
数据库建表
需要创建两张表:权限表和角色权限中间表
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接口
代码逻辑:
- 通过 Authentication 取出登录用户的所有 RoleName
- 通过RoleName获得RoleID(查询角色表)
- 根据RoleID查询对应的PermissionID(查询角色权限中间表,一对多关系)
- 根据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>
启动测试
但是如果访问user_r、user_c则会抛出异常
Bug JPA数据库查询一对多返回是同一条数据
但是代码运行后查出来的数据是同一条数据
原因:
是实体类中的@Id
注解
我把@Id注解放在了roleid上,但是在表中这个字段不是唯一的
解决方法:
所以加一个字段用来做主键id