springboot 整合 Spring Security 中篇(RBAC权限控制)

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>
  • 23
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
好的,以下是完整的Spring Boot 2.7.2整合Spring Security + Redis RBAC权限控制的Java代码示例: 首先,您需要在您的Spring Boot项目中添加Spring Security和Redis的依赖。 ```xml <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> ``` 接下来,创建一个User实体类,该类表示系统中的用户。 ```java @Data @AllArgsConstructor @NoArgsConstructor public class User implements UserDetails { private static final long serialVersionUID = -8091879091924046844L; private String username; private String password; private List<String> roles; @Override public Collection<? extends GrantedAuthority> getAuthorities() { return roles.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()); } @Override public boolean isAccountNonExpired() { return true; } @Override public boolean isAccountNonLocked() { return true; } @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return true; } } ``` 然后,创建一个UserDetailsServiceImpl类,该类实现了UserDetailsService接口,用于从Redis中获取用户信息。 ```java @Service public class UserDetailsServiceImpl implements UserDetailsService { @Autowired private RedisTemplate<String, Object> redisTemplate; @Override public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { User user = (User) redisTemplate.opsForHash().get("users", username); if (user == null) { throw new UsernameNotFoundException("User not found with username: " + username); } return user; } } ``` 接下来,创建一个RBACAuthorityService类,该类用于从Redis中获取角色和权限信息。 ```java @Service public class RBACAuthorityService { @Autowired private RedisTemplate<String, Object> redisTemplate; public List<String> getRolesByUserName(String username) { return (List<String>) redisTemplate.opsForHash().get("roles", username); } public List<String> getPermissionsByRole(String role) { return (List<String>) redisTemplate.opsForHash().get("permissions", role); } } ``` 然后,创建一个RBACPermissionEvaluator类,该类实现了PermissionEvaluator接口,用于判断用户是否有权限访问某个URL。 ```java @Component public class RBACPermissionEvaluator implements PermissionEvaluator { @Autowired private RBACAuthorityService rbacAuthorityService; @Override public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) { if (auth == null || targetDomainObject == null || !(permission instanceof String)) { return false; } String username = auth.getName(); List<String> roles = rbacAuthorityService.getRolesByUserName(username); for (String role : roles) { List<String> permissions = rbacAuthorityService.getPermissionsByRole(role); if (permissions.contains(permission)) { return true; } } return false; } @Override public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) { return false; } } ``` 然后,您需要创建一个RedisTokenRepositoryImpl类,该类实现了PersistentTokenRepository接口,用于将Remember-Me令牌存储到Redis中。 ```java @Repository public class RedisTokenRepositoryImpl implements PersistentTokenRepository { private RedisTemplate<String, Object> redisTemplate; public RedisTokenRepositoryImpl(RedisConnectionFactory redisConnectionFactory) { this.redisTemplate = new RedisTemplate<>(); this.redisTemplate.setConnectionFactory(redisConnectionFactory); this.redisTemplate.setKeySerializer(new StringRedisSerializer()); this.redisTemplate.setHashKeySerializer(new StringRedisSerializer()); this.redisTemplate.setHashValueSerializer(new GenericToStringSerializer<>(Object.class)); this.redisTemplate.afterPropertiesSet(); } @Override public void createNewToken(PersistentRememberMeToken token) { redisTemplate.opsForHash().put("rememberMeTokens", token.getSeries(), token); } @Override public void updateToken(String series, String tokenValue, Date lastUsed) { PersistentRememberMeToken token = getTokenForSeries(series); if (token != null) { token.setTokenValue(tokenValue); token.setDate(lastUsed); redisTemplate.opsForHash().put("rememberMeTokens", series, token); } } @Override public PersistentRememberMeToken getTokenForSeries(String seriesId) { return (PersistentRememberMeToken) redisTemplate.opsForHash().get("rememberMeTokens", seriesId); } @Override public void removeUserTokens(String username) { HashOperations<String, String, PersistentRememberMeToken> ops = redisTemplate.opsForHash(); Map<String, PersistentRememberMeToken> tokens = ops.entries("rememberMeTokens"); for (PersistentRememberMeToken token : tokens.values()) { if (username.equals(token.getUsername())) { ops.delete("rememberMeTokens", token.getSeries()); } } } } ``` 接下来,创建一个SecurityConfig类,该类用于配置Spring Security。 ```java @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Autowired private UserDetailsServiceImpl userDetailsService; @Autowired private RBACPermissionEvaluator permissionEvaluator; @Autowired private RedisConnectionFactory redisConnectionFactory; @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() .antMatchers("/admin/**").hasRole("ADMIN") .antMatchers("/user/**").hasAnyRole("ADMIN", "USER") .anyRequest().authenticated() .and() .formLogin() .loginPage("/login").permitAll() .and() .logout().permitAll() .and() .rememberMe() .tokenRepository(redisTokenRepository()) .tokenValiditySeconds(86400) .userDetailsService(userDetailsService); } @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder()); } @Override public void configure(WebSecurity web) throws Exception { web.ignoring().antMatchers("/css/**", "/js/**", "/images/**"); } @Bean public RedisTokenRepositoryImpl redisTokenRepository() { return new RedisTokenRepositoryImpl(redisConnectionFactory); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new RBACInterceptor(permissionEvaluator)); } } ``` 在上述代码中,我们使用了RBACPermissionEvaluator来判断用户是否有权限访问某个URL。我们还使用了RedisTokenRepositoryImpl来将Remember-Me令牌存储到Redis中。最后,我们使用了RBACInterceptor来拦截请求并进行权限验证。在addInterceptors方法中,我们将RBACInterceptor注册到Spring MVC拦截器链中。 最后,创建一个RBACInterceptor类,该类实现了HandlerInterceptor接口,用于拦截请求并进行权限验证。 ```java public class RBACInterceptor implements HandlerInterceptor { private RBACPermissionEvaluator permissionEvaluator; public RBACInterceptor(RBACPermissionEvaluator permissionEvaluator) { this.permissionEvaluator = permissionEvaluator; } @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String url = request.getRequestURI(); String method = request.getMethod(); Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); if (authentication == null || !authentication.isAuthenticated()) { response.sendRedirect("/login"); return false; } boolean hasPermission = permissionEvaluator.hasPermission(authentication, url, method); if (!hasPermission) { response.sendError(HttpServletResponse.SC_FORBIDDEN); return false; } return true; } } ``` 上述代码中,我们使用RBACPermissionEvaluator来判断用户是否有权限访问某个URL。如果用户没有权限访问某个URL,我们将返回HTTP 403 Forbidden错误。 希望这些信息能对您有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

江南一舟110

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值