引入依赖
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
<swagger.version>3.0.0</swagger.version>
<springboot.version>2.3.12.RELEASE</springboot.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional> <!-- 这个需要为 true 热部署才有效 -->
<scope>runtime</scope> <!--idea中需要指定-->
<version>${springboot.version}</version>
</dependency>
<!--Security 权限依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>${springboot.version}</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.28</version>
<scope>runtime</scope>
</dependency>
<!--springboot整合mybatis的依赖 -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.1</version>
</dependency>
<!--添加mybatis分页插件支持 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.0.0</version>
</dependency>
<!-- druid数据库链接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
<!-- 根据springfox-swagger3生成的数据,生成可视化的友好页面 -->
<dependency>
<groupId>io.springfox</groupId>
<artifactId>springfox-boot-starter</artifactId>
<version>${swagger.version}</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.auth0/java-jwt -->
<dependency>
<groupId>com.auth0</groupId>
<artifactId>java-jwt</artifactId>
<version>3.19.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/cn.hutool/hutool-all -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
<version>5.7.20</version>
</dependency>
</dependencies>
与Mybatis的整合配置
server:
port: 9001
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/hospitaldb?useUnicode=true;characterEncoding=utf8;useSSL=false;serverTimezone=UTC;
username: root
password: 123456
type: com.alibaba.druid.pool.DruidDataSource
mybatis:
mapper-locations: classpath:mapper/*.xml
type-aliases-package: org.example.pojo
configuration:
use-generated-keys: true
logging:
level:
org.example.mapper: debug
实现RBAC权限管理的SQL语句
-- 1、用户信息表
drop table if exists sys_user;
create table sys_user (
user_id bigint(20) not null auto_increment comment '用户ID',
dept_id bigint(20) default null comment '部门ID',
user_name varchar(64) default '' comment '用户昵称') engine=innodb auto_increment=100 comment = '用户信息表' default charset=utf8;
drop table if exists sys_role;
create table sys_role (
role_id bigint(20) not null auto_increment comment '角色ID',
role_name varchar(64) not null comment '角色名称',
role_code varchar(64) not null comment '角色权限字符串',
primary key (role_id)
) engine=innodb auto_increment=100 comment = '角色信息表' default charset=utf8;
insert into sys_role values('1', '管理员', 'admin');
insert into sys_role values('2', '普通角色', 'common');
insert into sys_role values('3', '超级管理员', 'root');
drop table if exists sys_menu;
create table sys_menu (
menu_id bigint(20) not null auto_increment comment '菜单ID',
menu_name varchar(64) not null comment '菜单名称',
parent_id bigint(20) default 0 comment '父菜单ID',
order_num int(4) default 0 comment '显示顺序',
path varchar(255) default '#' comment '请求地址,菜单URL',
component varchar(32) default '' comment '前端页面组件名',
menu_type char(1) default '' comment '菜单类型(M目录 C菜单 F按钮)',
statu char(1) default 0 comment '菜单状态(0显示 1隐藏)',
permission varchar(128) default null comment '权限标识,如system:user:view',
primary key (menu_id),
UNIQUE key `menu_name`(`menu_name`) using BTREE
) engine=innodb auto_increment=2000 comment = '菜单权限表',default CHARSET=utf8;
-- ----------------------------
-- 初始化-菜单信息表数据
-- ----------------------------
-- 一级菜单ome
insert into sys_menu values('1', '系统管理', '0', '1', '/home', 'HOME', 'M', '0', '');
insert into sys_menu values('2', '系统工具', '0', '3', '/tools', 'TOOLS', 'M', '0', '');
insert into sys_menu values('3', '考试子系统', '0', '3', '/exam', 'EXAM', 'M', '0', '');
insert into sys_menu values('4', '关于', '0', '4', 'http://ruoyi.vip', 'menuBlank','C', '0', '');
-- 二级菜单
insert into sys_menu values('100', '用户管理', '1', '1', '/system/user', '', 'C', '0', 'system:user:view');
insert into sys_menu values('101', '角色管理', '1', '2', '/system/role', '', 'C', '0', 'system:role:view');
insert into sys_menu values('102', '菜单管理', '1', '3', '/system/menu', '', 'C', '0', 'system:menu:view');
insert into sys_menu values('103', '部门管理', '1', '4', '/system/dept', '', 'C', '0', 'system:dept:view');
insert into sys_menu values('104', '岗位管理', '1', '5', '/system/post', '', 'C', '0', 'system:post:view');
insert into sys_menu values('105', '字典管理', '1', '6', '/system/dict', '', 'C', '0', 'system:dict:view');
insert into sys_menu values('106', '参数设置', '1', '7', '/system/config', '', 'C', '0', 'system:config:view');
insert into sys_menu values('107', '通知公告', '1', '8', '/system/notice', '', 'C', '0');
insert into sys_menu values('108', '日志管理', '1', '9', '#', '', 'M', '0', '');
insert into sys_menu values('109', '在线用户', '2', '1', '/monitor/online', '', 'C', '0', 'monitor:online:view');
insert into sys_menu values('110', '定时任务', '2', '2', '/monitor/job', '', 'C', '0', 'monitor:job:view',);
drop table if exists sys_user_role;
create table sys_user_role (
user_id bigint(20) not null comment '用户ID',
role_id bigint(20) not null comment '角色ID',
primary key(user_id, role_id)
) engine=innodb comment = '用户和角色关联表';
-- ----------------------------
-- 初始化-用户和角色关联表数据
-- ----------------------------
insert into sys_user_role values ('1', '1');
insert into sys_user_role values ('103', '1');
insert into sys_user_role values ('101', '2');
-- ----------------------------
-- 7、角色和菜单关联表 角色1-N菜单
-- ----------------------------
drop table if exists sys_role_menu;
create table sys_role_menu (
role_id bigint(20) not null comment '角色ID',
menu_id bigint(20) not null comment '菜单ID',
primary key(role_id, menu_id)
) engine=innodb comment = '角色和菜单关联表';
-- ----------------------------
-- 初始化-角色和菜单关联表数据
-- ----------------------------
insert into sys_role_menu values ('1', '1');
insert into sys_role_menu values ('1', '2');
insert into sys_role_menu values ('1', '3');
insert into sys_role_menu values ('1', '4');
insert into sys_role_menu values ('1', '100');
insert into sys_role_menu values ('1', '101');
insert into sys_role_menu values ('1', '102');
insert into sys_role_menu values ('1', '103');
insert into sys_role_menu values ('1', '104');
insert into sys_role_menu values ('1', '105');
insert into sys_role_menu values ('1', '106');
insert into sys_role_menu values ('1', '107');
insert into sys_role_menu values ('1', '108');
完成SpringSecurity配置
Spring Security对哪些接口进行保护、什么组件生效、某些功能是否启用等等都需要在配置类中进行配置。
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfigurationAdapter extends WebSecurityConfigurerAdapter {
//定义放行的url,即白名单
private static final String[] URL_WHITE_LIST={
"/login/auth",
"/login/user",
"/kaptcha",
"/hello",
"/test/pwd",
"/swagger-ui/",
"favicon.ico"
};
@Autowired
private LoginFailureHandler loginFailureHandler;
@Autowired
private LoginSuccessHandler loginSuccessHandler;
@Autowired
private JwtAccessDenidHandler jwtAccessDenidHandler;
@Autowired
private MyEntryPoint myEntryPoint;
@Autowired
private JwtLogoutSuccessHandle jwtLogoutSuccessHandle;
//向容器注入JWT过滤器对象
@Bean
public JwtFilter jwtFilter() throws Exception {
return new JwtFilter(authenticationManager());
}
//注入加密接口对象
@Bean("passwordEncoder")
public PasswordEncoder passwordEncoder(){
return new BCryptPasswordEncoder();
}
//注入授权管理器对象
@Bean
@Override
protected AuthenticationManager authenticationManager() throws Exception {
return super.authenticationManager();
}
//配置security
@Override
protected void configure(HttpSecurity http) throws Exception {
System.out.println("进入spring security 配置...");
//开启跨域以便前端调用接口
http.cors().
and() //在Security的默认拦截器里,默认会开启CSRF处理,判断请求是否携带了token,如果没有就拒绝访问
.csrf().disable() //关闭csrf和frameOptions,如果不关闭会影响前端请求接口
//登录配置
.formLogin()
.successHandler(loginSuccessHandler)
.failureHandler(loginFailureHandler)
.and() //注销退出处理
.logout()
.logoutSuccessHandler(jwtLogoutSuccessHandle)
//禁用session
.and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
//配置拦截规则,这是配置的关键,决定哪些接口开启防护,哪些接口绕过防护
.and()
.authorizeRequests()
.antMatchers(URL_WHITE_LIST).permitAll() //放行的URL请求,并授权
.anyRequest().authenticated() //这里意思是其它所有接口需要认证才能访问
// 指定认证错误处理器
.and()
.exceptionHandling()
.authenticationEntryPoint(myEntryPoint)
.accessDeniedHandler(jwtAccessDenidHandler) //1次性消费类,可使用lambda表达式
// 配置自定义的过滤器
.and()
.addFilter(jwtFilter())
;
}
@Resource
private MyUserDetailsServiceImpl userDetailsService;
//通过构建,注入MyUserDetailsServiceImpl的bean
@Override
protected void configure(AuthenticationManagerBuilder builder) throws Exception {
builder.userDetailsService(userDetailsService);
//.passwordEncoder(passwordEncoder());
}
}
完成userDetails类
//用于封装登录用户的名称、密码、权限列表信息
public class MyUserDetails extends User {
public MyUserDetails(String username, String password, Collection<? extends GrantedAuthority> authorities) {
super(username, password, authorities);
}
}
完成MyUserDetailsServiceImpl
定义MyUserDetailsMapper,通过用户名称、id,查询用户信息,获取角色、权限列表。
public interface MyUserDetailsMapper {
//根据userID查询用户信息
@Select("SELECT login_name,password,statu\n" +
"FROM sys_user u\n" +
"WHERE u.login_name = #{userId}")
MyUserDetails findByUserName(@Param("userId") String userId);
//根据userName查询用户角色
@Select("SELECT role_code\n" +
"FROM sys_role r\n" +
"LEFT JOIN sys_user_role ur ON r.role_id = ur.role_id\n" +
"LEFT JOIN sys_user u ON u.user_id = ur.user_id\n" +
"WHERE u.user_name = #{userName}")
List<String> findRoleByUserName(@Param("userId") String userName);
//根据userID查询用户角色
@Select("SELECT role_code\n" +
"FROM sys_role r\n" +
"LEFT JOIN sys_user_role ur ON r.role_id = ur.role_id\n" +
"LEFT JOIN sys_user u ON u.user_id = ur.user_id\n" +
"WHERE u.user_id = #{userId}")
List<String> findRoleByUserId(@Param("userId") int userId);
//根据用户角色查询用户权限
@Select({
"<script>",
"SELECT permission " ,
"FROM sys_menu m " ,
"LEFT JOIN sys_role_menu rm ON m.menu_id = rm.menu_id " ,
"LEFT JOIN sys_role r ON r.role_id = rm.role_id ",
"WHERE r.role_code IN ",
"<foreach collection='roleCodes' item='roleCode' open='(' separator=',' close=')'>",
"#{roleCode}",
"</foreach>",
"</script>"
})
List<String> findAuthorityByRoleCodes(@Param("roleCodes") List<String> roleCodes);
}
完成 MyUserDetailsServiceImpl类,这里使用了前面定义的MyUserDetails 类。
@Component //
public class MyUserDetailsServiceImpl implements UserDetailsService{
@Resource
private MyUserDetailsMapper myUserDetailsMapper;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
//1.获得用户信息
MyUserDetails myUserDetails=myUserDetailsMapper.findByUserName(username);
if (myUserDetails==null){
throw new UsernameNotFoundException("用户名不存在!");
}
//2.获得用户角色列表(一个用户可能有多个角色
List<String> roles=myUserDetailsMapper.findRoleByUserName(username);
//3.通过角色列表获取权限列表(一个角色可能有多个权限
List<String> authorities=myUserDetailsMapper.findAuthorityByRoleCodes(roles);
//4.为角色标识加上ROLE_前缀(Spring Security规范)
roles=roles.stream()
.map(rc->"ROLE"+rc)
.collect(Collectors.toList());
//5.角色是一种特殊的权限,所以合并
authorities.addAll(roles);
//转成用逗号分隔的字符串,为用户设置权限标识 myUserDetails.setAuthorities(AuthorityUtils.commaSeparatedStringToAuthorityList(
String.join(",", authorities)));
return myUserDetails;
}
public List<GrantedAuthority> getAuthority(int userId){
//2.获得用户角色列表(一个用户可能有多个角色
List<String> roles=myUserDetailsMapper.findRoleByUserId(userId);
System.out.println(roles);
//3.通过角色列表获取权限列表(一个角色可能有多个权限
List<String> authorities=myUserDetailsMapper.findAuthorityByRoleCodes(roles);
//4.为角色标识加上ROLE_前缀(Spring Security规范)
roles=roles.stream()
.map(rc->"ROLE_"+rc)
.collect(Collectors.toList());
//5.角色是一种特殊的权限,所以合并
authorities.addAll(roles);
System.out.println(authorities);
//转成用逗号分隔的字符串,为用户设置权限标识
List<GrantedAuthority> authoritiesList=AuthorityUtils.commaSeparatedStringToAuthorityList(
String.join(",", authorities));
return authoritiesList;
}
}
整合JWT
public class JwtFilter extends BasicAuthenticationFilter {
@Resource
private UserService userService;
@Resource
private MyUserDetailsServiceImpl userDetailsService;
public JwtFilter(AuthenticationManager authenticationManager) {
super(authenticationManager);
}
//对http请求过滤处理
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException {
//1。取出请求头中包含的token
String token=request.getHeader("Authorization");
if (token==null || token.length()<1){//token不合法,过滤放行
chain.doFilter(request,response);
return;
}
//2.解析token,进行token合法性验证
JWT jwtToken= JWTUtil.parseToken(token);
if (jwtToken.verify()){
throw new JWTException("无效的token,请重新登录");
}
//3.合法的token,则对用户进行权限鉴定
//从token中解析出登录用户名称
String username=jwtToken.getPayload(ConstUtil.JWT_TOKEN_LOAD_NAME).toString();
System.out.println("JWT,解析token,用户:"+username);
User user=userService.getByName(username);
if (user==null){
throw new JWTException("无效的token,用户不存在!");
}
//生成授权对象,包含用户名称、密码、权限列表
Authentication authentication=new UsernamePasswordAuthenticationToken(username,
user.getPassword(),userDetailsService.getAuthorities(user));
//把授权对象加入security context中
SecurityContextHolder.getContext().setAuthentication(authentication);
chain.doFilter(request,response);//过滤器连继续处理其它请求
}
}
登录处理
//用户登录处理
@PostMapping("/login/auth")
public ResponseResult login(@RequestBody User user){//注解传入json格式对象数据
System.out.println(user);
ResponseResult result = new ResponseResult(-1,"登录失败",null);
//请求业务,查询注册用户
User signinUser=userService.findUserByNamePwd(user.getName(),user.getPassword());
if (signinUser!=null){
//生成token
String token=createToken(signinUser);
//生成UserVo对象,封装了token及签名用户信息
result.success("登录成功",new UserVo(token,signinUser));
}
return result;
}
//使用hutool工具包来生成token
private String createToken(User user) {
DateTime now = DateTime.now();
DateTime newTime = now.offsetNew(DateField.MINUTE, ConstUtil.JWT_EXPIRE_TIME);
Map<String,Object> payload = new HashMap<String,Object>();
//签发时间
payload.put(JWTPayload.ISSUED_AT, now);
//过期时间
payload.put(JWTPayload.EXPIRES_AT, newTime);
//生效时间
payload.put(JWTPayload.NOT_BEFORE, now);
//载荷
payload.put(ConstUtil.JWT_TOKEN_LOAD_NAME, user.getName());
payload.put(ConstUtil.JWT_TOKEN_LOAD_PASSWORD, user.getPassword());
String key = ConstUtil.JWT_TOKEN_KEY;
String token = JWTUtil.createToken(payload, key.getBytes());
//System.out.println(token);
return token;
}
整合完成。
下面来看看security内置注解的用法
使用security内置权限注解
Security权限注解
@PreAuthorize:在方法执行前进行权限检查
@PosAuthorize:在方法执行后进行权限检查
@Secured:类似@PreAuthorize
在代码中使用security权限注解
```csharp
```csharp
//添加用户
@PostMapping("/user")
@Secured("hasAuthority('system:user:add')") //要求操作用户具有‘system:user:add’权限
public ResponseResult addUser(@RequestBody User user){ //传入json数据
//public ResponseResult addUser(User user){ //传入form表单数据
System.out.println("add user!");
System.out.println(user);
ResponseResult result=new ResponseResult(-1,"添加用户失败",false);
//调业务方法
boolean bn=userService.add(user);
if (bn){
result.success(true);
}
return result;
}
//删除用户
@PreAuthorize("hasRole('admin')") //要求用户具有admin角色权限
@DeleteMapping("/user/{id}")
public ResponseResult deleteUser(@PathVariable Integer id){
ResponseResult result=new ResponseResult(-1,"删除失败",false);
boolean bn=userService.delete(id);
if (bn){
return result.success(bn);
}
return result;
}