1.先了解RBAC 是什么
RBAC(Role-Based Access control) ,也就是基于角色的权限分配解决方案
2.数据库读取用户信息和授权信息
1.上篇用户名好授权等信息都是从内存读取实际情况都是从数据库获取;
主要设计两个类
UserDetails和UserDetailsService
看下继承图:
不难看出 InMemoryUserDetailsManager 继承UserDetailsService
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
package org.springframework.security.core.userdetails;
import java.io.Serializable;
import java.util.Collection;
import org.springframework.security.core.GrantedAuthority;
public interface UserDetails extends Serializable {
Collection<? extends GrantedAuthority> getAuthorities();
String getPassword();
String getUsername();
boolean isAccountNonExpired();
boolean isAccountNonLocked();
boolean isCredentialsNonExpired();
boolean isEnabled();
}
不难看出UserDetails 也就是和普通的pojo
综上所述,只需要写一个类实现UserDetailsService 接口的方法和写一个普通的javaBean 继承UserDetails,交给spring 容器也就完成了自定义用户的信息
3.创建5张表(用户表,角色表,权限表,用户角色表,角色权限表)
create table sys_user (id int auto_increment primary key ,
`name` varchar(50) unique not null comment '用户名' ,
`password` varchar(200) comment '密码',
`is_expired` int default 1 comment '登陆是否过期',
`is_locked` int default 1 comment '账号是否锁定',
`is_enable` int default 1 comment '账号是否可用',
`credentials_expired` int default 1 comment '凭证过期');
select * from sys_user;
添加两条数据测试 密码都是123456
create table sys_roles (id int auto_increment primary key ,
`name` varchar(200) not null comment '角色名称'
);
create table sys_authority(id int auto_increment primary key ,
`authority` varchar(120) not null
);
备注;老师和学生,老师可以删除作业。查看作业。修改作业
学生拥有查看,修改作业
create table `sys_user_roles` (`user_id` int not null comment '用户id',
`roles_id` int not null comment '校色id'
);
为用户张三和小李分配角色
张三拥有学生的权限,小李拥有老师权限
create table `sys_roles_authority` (id int not null comment '角色id',
`authority_id` int not null comment '权限id'
);
添加两个角色。角色1 学生 角色2 老师
3.定义一个类实现UserDetailsService交给spring 容器调度
主要在这个方法loadUserByUsername 从数据库读取用户的登录信息和权限信息
package com.example.testsecurity.service;
import com.example.testsecurity.mapper.UserMapper;
import com.example.testsecurity.pojo.MyUser;
import com.example.testsecurity.pojo.User;
import com.example.testsecurity.pojo.UserAuthority;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;
import java.util.Objects;
@Service
@Slf4j
public class MyUserService implements UserDetailsService {
@Autowired
private UserMapper userMapper;
@Autowired
private UserAuthorityService userAuthorityService;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userMapper.findUserByName(username);
UserAuthority userAuthority = userAuthorityService.findAuthorByUserId(user.getId());
log.info(userAuthority.getAuthority());
if (Objects.isNull(user)) {
throw new UsernameNotFoundException("用户名不存在");
}
log.info("user" + username);
MyUser myUser = new MyUser(user, userAuthority);
return myUser;
}
}
4.定义一个类实现UserDetails
实体类:
package com.example.testsecurity.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.stereotype.Component;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Component
public class User {
private int id;
private String name;
private int roleId;
@JsonIgnore
private String password; //忽略返回密码
private int isExpired;
private int isLocked;
private int isEnable;
private int credentialsExpired;
}
package com.example.testsecurity.pojo;
import com.example.testsecurity.service.UserAuthorityService;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
//自定义用户信息
@Component
@AllArgsConstructor
@Data
@NoArgsConstructor
@Slf4j
public class MyUser implements UserDetails {
private User user;
private UserAuthority userAuthority;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
String[] authorities = userAuthority.getAuthority().split(","); //由于数据库中authority字段里面是用","来分隔每个权限
List<SimpleGrantedAuthority> simpleGrantedAuthorities = new ArrayList<>();
for (String role : authorities) {
simpleGrantedAuthorities.add(new SimpleGrantedAuthority(role));
}
log.info(simpleGrantedAuthorities.get(0).getAuthority());
return simpleGrantedAuthorities;
}
@Override
public String getPassword() {
String password = user.getPassword();
user.setPassword(null);// 擦除密码防止传到前端
return password;
}
@Override
public String getUsername() {
return user.getName();
}
@Override
public boolean isAccountNonExpired() {
return user.getIsExpired() == 1;
}
@Override
public boolean isAccountNonLocked() {
return user.getIsLocked() == 1;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return user.getIsEnable() == 1;
}
}
package com.example.testsecurity.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserAuthority {
private int id;
private String authority;
}
5.编写测试controller
package com.example.testsecurity.controller;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class TestController {
@GetMapping("/lookZy")
@PreAuthorize("hasAuthority('look:zy')")
public String lookZy() {
return "查看作业";
}
@GetMapping("/editZy")
@PreAuthorize("hasAuthority('edit:zy')")
public String editZy() {
return "编辑作业";
}
@GetMapping("/delZy")
@PreAuthorize("hasAnyAuthority('del:zy')")
public String delZy() {
return "删除作业";
}
}
6.编写配置类并开启方法授权
package com.example.testsecurity.config;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
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.WebSecurityCustomizer;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
//从 Spring Security 5.7.0-M2开始 WebSecurityConfigurerAdapter 被标记为过期,鼓励用户转向基于组件的 security 配置
@Configuration
@Slf4j
//全局方法授权
@EnableWebSecurity // 启用SpringSecurity
@EnableMethodSecurity
public class SecurityConfiguration {
//认证成功处理器
@Autowired
private AuthorSuccesssHandler authorSuccesssHandler;
@Autowired
//认证失败处理器
private AuthorFailHandler authorFailHandler;
//退出登录处理器
@Autowired
private AppLoginOutHandler appLoginOutHandler;
//访问拒绝处理器
@Autowired
private AppAcessDeiedHandler appAcessDeiedHandler;
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry -> authorizationManagerRequestMatcherRegistry
.anyRequest().authenticated());
http.formLogin()
.successHandler(authorSuccesssHandler)//认证成功
.failureHandler(authorFailHandler)//认证失败
.permitAll();
http.logout().logoutSuccessHandler(appLoginOutHandler); //退出登录
http.exceptionHandling().accessDeniedHandler(appAcessDeiedHandler);//访问资源失败
return http.build();
}
// @Bean
// public WebSecurityCustomizer webSecurityCustomizer() {
// return (web) -> web.ignoring().requestMatchers("/test/**");
// }
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
7.登录测试查看用户信息并测试权限
张三用户信息
小李信息:
张三学生、和小李都可以访问的接口
小李可以访问的
张三访问不了资源
备注:两个mapper代码
package com.example.testsecurity.mapper;
import com.example.testsecurity.pojo.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface UserMapper {
@Select("select * from sys_user where name=#{username}")
User findUserByName(String username);
}
package com.example.testsecurity.mapper;
import com.example.testsecurity.pojo.UserAuthority;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface UserAuthorityMapper {
UserAuthority findAuthorByUserId(Integer userId);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- namespace等于mapper接口类的全限定名,这样实现对应 -->
<mapper namespace="com.example.testsecurity.mapper.UserAuthorityMapper">
<select id="findAuthorByUserId" resultType="com.example.testsecurity.pojo.UserAuthority">
SELECT * FROM `sys_authority`
WHERE id=(
SELECT `authority_id` from `sys_roles_authority`
WHERE id=(SELECT `roles_id` FROM `sys_user_roles`
WHERE `user_id`=#{userId}));
</select>
</mapper>