• Springboot+Spring secuirty 前后端分离后台菜单权限设计


    背景:菜单和权限在系统中是非常重要的事情,在结合自己研究过的Spring security和项目前后端实践中对进行总结。

    介绍:使用基于RBAC权限模型,针对角色分配不同的权限

    数据库设计:

    系统菜单                                                系统角色                                              菜单角色表

                                  

      用户对应的角色                               用户信息

                     

    技术:Spring security+jjwt

    Spring security:是Spring 开源的权限管理框架,由一组过滤器链组成,对不同的访问进去拦截和控制,也可以自己实现权限拦截

    spring security 的核心功能主要包括:

    • 认证 (你是谁)
    • 授权 (你能干什么)
    • 攻击防护 (防止伪造身份)

    jjwt:是一个提供端到端的JWT创建和验证的Java库,可以生成加密的token,并可以从token反推出存放在token的一些信息(如用户账号)——参考官网https://jwt.io/introduction/

    实现:通过UserDetailsService 和UserDetails 通过数据库获取用户信息如(权限,用户账号)

    步骤一:

    // 定义jjwt的用户的一些信息,在后面生成token时需要,并且Spring security要获取实现UserDetails 接口用户信息  
    @Getter @AllArgsConstructor
    public class SystemUser implements UserDetails { @JSONField(serialize = false) private final Long id; private final String username; @JSONField(serialize = false) private final String password; public Long getId() { return id; } private final String salt;
    // 权限 @JSONField(serialize
    = false) private final Collection<GrantedAuthority> authorities; @JSONField(serialize = false) @Override public boolean isAccountNonExpired() { return true; } @JSONField(serialize = false) @Override public boolean isAccountNonLocked() { return true; } @JSONField(serialize = false) @Override public boolean isCredentialsNonExpired() { return true; } @Override public boolean isEnabled() { return false; } @JSONField(serialize = false) @Override public String getPassword() { return password; } public Collection getRoles() { return authorities.stream().map(GrantedAuthority::getAuthority).collect(Collectors.toSet()); } }

    步骤二:实现 UserDetailsService 接口,这里我使用mybatis查询数据库,通过用户账号获取数据库用户信息

    public class SystemUserDetailsService implements UserDetailsService {
    
        @Autowired
        private ISysUserService userService;
    
        @Autowired
        private JwtPermissionService permissionService; // 获取用户角色的菜单权限
    
        @Override
        public UserDetails loadUserByUsername(String username) {
            SysUser user = userService.findByName(username);
            if (user == null) {
                throw new ServiceException("账号不存在");
            } else {
                if (user.getUserStatus().equals(Constants.OrganizationStatus.DISABLE)) {
                    throw new ServiceException("账号已被禁用");
                }
                return createJwtUser(user);
            }
        }
    
        public UserDetails createJwtUser(SysUser user) {
            return new SystemUser(
                    user.getId(),
                    user.getUsername(),
                    user.getPassword(),
                    user.getSalt(),
                    permissionService.mapToGrantedAuthorities(user),
                    user.getCreateTime()
            );
        }
    }
    

      

    步骤三:JwtPermissionService 实现,请注意这是实现的虚假逻辑,具体的还要看业务逻辑

    @Component
    public class JwtPermissionService{

    @Autowired
    private IUsersRolesService usersRolesService;
    @Autowired
    private IRolesMenuService rolesMenuService;

    public Collection<GrantedAuthority> mapToGrantedAuthorities(SysUser user){
    // step 1 根据用户账号获取用户的角色
    Set<Role> menu =usersRolesService.getRole(String userName);
    // step 2 根据角色获取用户的菜单
    Set<Menu>menuList=rolesMenuService.getMenu(Set<Role>role);
     // step 3 获取菜单对应的menu_make 进行转换

    return menuVos.stream().filter(x -> !StringUtils.isEmpty(x.getMenuMark())).map(result -> {

    String permission =result.getMenuMark();
    return new SimpleGrantedAuthority(permission);}
    ).collect(Collectors.toList());
    }
    }

     步骤4:定义Spring security  权限配置

    @Configuration
    @EnableWebSecurity
    @EnableGlobalMethodSecurity(prePostEnabled = true)
    public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtTokenFilter tokenFilter;
    @Autowired
    public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
    auth
    .userDetailsService(jwtUserDetailsService)
    .passwordEncoder(passwordEncoderBean());
    }
    @Bean
    GrantedAuthorityDefaults grantedAuthorityDefaults() {
    // 去掉前缀
    return new GrantedAuthorityDefaults("");
    }

    // 加密方式
    @Bean
    public PasswordEncoder passwordEncoderBean() {
    return new BCryptPasswordEncoder();
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
    }// 权限拦截规则,千万不要.login() 这直接走表单验证了,会比较麻烦
    @Override
    protected void configure(HttpSecurity httpSecurity) throws Exception {

    httpSecurity

    // 禁用 CSRF
    .csrf().disable()
    // 不创建会话
    .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
    // 过滤请求
    .authorizeRequests()
    .antMatchers(
    HttpMethod.GET,
    "/*.html",
    "/**/*.html",
    "/**/*.css",
    "/**/*.js"
    ).anonymous()

    .antMatchers( HttpMethod.POST,"/auth/login).permitAll()
    .antMatchers("/websocket/**").anonymous()
    // 所有请求都需要认证
    .anyRequest().authenticated()
    // 防止iframe 造成跨域
    .and().headers().frameOptions().disable();
    // 添加自定义拦截器
    httpSecurity
    .addFilterBefore(
    tokenFilter,UsernamePasswordAuthenticationFilter.class);
        }}

     步骤4:自定义拦截器,通过此拦截器, 前端访问时候头部要带"Authorization",通过token获取用户信息

    @Component
    public class JwtTokenFilter extends OncePerRequestFilter {
    @Autowired
    private UserDetailsService userDetailsService;
     
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {
    String requestHeader = request.getHeader("Authorization");
    String authToken=null;
    if (requestHeader != null && requestHeader.startsWith("Bearer ")) {
    authToken = requestHeader.substring(7);
    String userName =Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken ).getBody().getSubject();

    }

    if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
    SystemUser userDetails = (SystemUser ) this.userDetailsService.loadUserByUsername(username);
    UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
    SecurityContextHolder.getContext().setAuthentication(authentication);
    chain.doFilter(request, response);
    }
    }
    }

    步骤五:登录返回toekn 给前端

    @Getter
    @AllArgsConstructor
    public class AuthenticationInfo implements Serializable {

    private final String token;

    private final JwtUser user;
    }

    // 登录构造器

    @RequestMapping("auth") public class SecurityController{
    @Autowired
    @Qualifier("SystemUserDetailsService")
    private UserDetailsService userDetailsService;
    @PostMapping(value = "${jwt.auth.path}")
    public AuthorizationUser login(@RequestParam("userName")String userName,@RequestParam("password")String password)) {
        final SystemUser jwtUser = (SystemUser ) userDetailsService.loadUserByUsername(userName);
        //获取用户的token,是否存在
    Date expirationDate = new Date(createdDate.getTime() +864000);
        String token =Jwts.builder()
    .setClaims(claims)
    .setSubject()
    .setIssuedAt(new Date)
    .setExpiration(expirationDate)
    .signWith(SignatureAlgorithm.HS512, secret)
    .compact();

    return new AuthenticationInfo(token, jwtUser));
    }

    }

    步骤6 定义具有某个菜单的构造器,前端通过定义菜单标识跟后台@PreAuthorize 对应的权限进行关联起来,这样就可以形成对应的权限

    @RequestMapping("/admin")
    public class Demo {
    
    @RequestMapping("/pageList")
    @PreAuthorize("hasAnyRole(‘menu_mark’)")
    public List<String> pageList(){
    return new ArrayList();
    }
    }
  • 相关阅读:
    SpringCloud之Eureka注册中心原理及其搭建
    微服务架构及其概念
    SpringBoot(十六)-----Springboot整合JPA
    SpringBoot(十五)-----Springboot配合JDBCTemplate实现增删改查
    MYSQL安装报错 -- 出现Failed to find valid data directory.
    SpringBoot(十四)-----异常处理
    JQuery 隔行变色
    C#断开式连接
    C# 学生表的插入操作
    C#字符串
  • 原文地址:https://www.cnblogs.com/uqing/p/12831065.html
Copyright © 2020-2023  润新知