从数据库查询权限信息与自定义失败处理

Posted 生产队的大驴and小乌贼来了

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从数据库查询权限信息与自定义失败处理相关的知识,希望对你有一定的参考价值。

从数据库查询权限信息

      代码实现

        我们只需要根据用户id去查询到其所对应的权限信息即可。

​           所以我们可以先定义个mapper,其中提供一个方法可以根据userid查询权限信息。

package com.example.qinghuatokendemo.Mapper;

import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.example.qinghuatokendemo.Domain.Menu;
import org.apache.ibatis.annotations.Mapper;

import java.util.List;
@Mapper
public interface MenuMapper extends BaseMapper<Menu> 

    List<String> selectPermsByUserId(Long userid);

尤其是自定义方法,所以需要创建对应的mapper文件,定义对应的sql语句

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.qinghuatokendemo.Mapper.MenuMapper">
    <select id="selectPermsByUserId" resultType="java.lang.String">
        SELECT
            DISTINCT m.`perms`
        FROM
            sys_user_role ur
                LEFT JOIN `sys_role` r ON ur.`role_id` = r.`id`
                LEFT JOIN `sys_role_menu` rm ON ur.`role_id` = rm.`role_id`
                LEFT JOIN `sys_menu` m ON m.`id` = rm.`menu_id`
        WHERE
            user_id = #userid
          AND r.`status` = 0
          AND m.`status` = 0
    </select>
</mapper>

在application.yml中配置mapperXML文件的位置

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springsecurity?characterEncoding=utf-8&serverTimezone=UTC
    username: root
    password: njzyb555
    driver-class-name: com.mysql.cj.jdbc.Driver
mybatis-plus:
  mapper-locations: classpath*:/mapper/**/*.xml

 

测试类

package com.example.qinghuatokendemo;


import com.example.qinghuatokendemo.Domain.User;
import com.example.qinghuatokendemo.Mapper.MenuMapper;
import com.example.qinghuatokendemo.Mapper.UserMapper;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

import java.util.List;

@SpringBootTest
public class MapperTest 

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private MenuMapper menuMapper;

    @Autowired
    private PasswordEncoder passwordEncoder;

    @Test
    public void TestBCryptPasswordEncoder()
        //$2a$10$9CmQULPcw8prFL.gnmM/zO1bDtPtVNb4mTxNs2wHsm7xonGMCvT2C

        System.out.println(passwordEncoder.
                matches("1234", "$2a$10$eAQvguaa3mHMt7cUrXeQnu3vIw74tbNtthm/t1gH6IMrRihv1OpRu"));
        /*String encode = passwordEncoder.encode("1234");
        System.out.println(encode);*/
    

    @Test
    public void testUser()
        List<User> users = userMapper.selectList(null);
        System.out.println(users);
    


    @Test
    public void testselectPermsByUserId()
        List<String> list = menuMapper.selectPermsByUserId(1L);
        System.out.println(list);
    

 

 

 

自定义失败处理

  

​ 我们还希望在认证失败或者是授权失败的情况下也能和我们的接口一样返回相同结构的json,这样可以让前端能对响应进行统一的处理。要实现这个功能我们需要知道SpringSecurity的异常处理机制。

​ 在SpringSecurity中,如果我们在认证或者授权的过程中出现了异常会被ExceptionTranslationFilter捕获到。在ExceptionTranslationFilter中会去判断是认证失败还是授权失败出现的异常。

​ 如果是认证过程中出现的异常会被封装成AuthenticationException然后调用AuthenticationEntryPoint对象的方法去进行异常处理。

​ 如果是授权过程中出现的异常会被封装成AccessDeniedException然后调用AccessDeniedHandler对象的方法去进行异常处理。

​ 所以如果我们需要自定义异常处理,我们只需要自定义AuthenticationEntryPoint和AccessDeniedHandler然后配置给SpringSecurity即可。

自定义实现类

package com.example.qinghuatokendemo.Handler;

import com.alibaba.fastjson.JSON;
import com.example.qinghuatokendemo.Domain.ResponseResult;
import com.example.qinghuatokendemo.Utils.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class AccessDeniedHandlerImpl implements AuthenticationEntryPoint 
    @Override
    public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException 
        ResponseResult result = new ResponseResult(HttpStatus.INTERNAL_SERVER_ERROR.value(),"用户认证失败请查询登录");
        String json = JSON.toJSONString(request);
        //处理异常
        WebUtils.renderString(response,json);
    

 

package com.example.qinghuatokendemo.Handler;

import com.alibaba.fastjson.JSON;
import com.example.qinghuatokendemo.Domain.ResponseResult;
import com.example.qinghuatokendemo.Utils.WebUtils;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

@Component
public class AccessDeniedHandlerImpl implements AccessDeniedHandler 
    @Override
    public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException 
        ResponseResult result = new ResponseResult(HttpStatus.FORBIDDEN.value(), "权限不足");
        String json = JSON.toJSONString(result);
        WebUtils.renderString(response,json);

    

 

package com.example.qinghuatokendemo.Config;

import com.example.qinghuatokendemo.Handler.AccessDeniedHandlerImpl;
import com.example.qinghuatokendemo.filter.JwtAuthenticationTokenFilter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

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

    @Bean
    public PasswordEncoder passwordEncoder()
        return new BCryptPasswordEncoder();
    

    @Autowired
    JwtAuthenticationTokenFilter jwtAuthenticationTokenFilter;

    @Autowired
    private AuthenticationEntryPoint authenticationEntryPoint;

    @Autowired
    private AccessDeniedHandler accessDeniedHandler;

    @Override
    protected void configure(HttpSecurity http) throws Exception 
       /* http
                //关闭csrf
                .csrf().disable()
                //不通过Session获取SecurityContext
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                // 对于登录接口 允许匿名访问
                .antMatchers("/user/login").anonymous()
                // 除上面外的所有请求全部需要鉴权认证
                .anyRequest().authenticated();*/
        http
                .csrf().disable()
                .sessionManagement()
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                .and()
                .authorizeRequests()
                .antMatchers("/hello").permitAll()
                .antMatchers("/user/login").anonymous()
                .anyRequest().authenticated();


        //把token校验过滤器添加到过滤器链中
        http.addFilterBefore(jwtAuthenticationTokenFilter, UsernamePasswordAuthenticationFilter.class);

        //配置异常处理器
        http.exceptionHandling()
                //认证失败处理器
                .authenticationEntryPoint(authenticationEntryPoint).accessDeniedHandler(accessDeniedHandler);
    

    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception 
        return super.authenticationManagerBean();
    

 

 

 

 

 

 

Django(63)drf权限源码分析与自定义权限

前言

上一篇我们分析了认证的源码,一个请求认证通过以后,第二步就是查看权限了,drf默认是允许所有用户访问
 

权限源码分析

源码入口:APIView.py文件下的initial方法下的check_permissions

def check_permissions(self, request):
    """
    检查是否应允许该请求。如果请求不被允许,则引发适当的异常。
    """
    for permission in self.get_permissions():
        if not permission.has_permission(request, self):
            self.permission_denied(
                request,
                message=getattr(permission, \'message\', None),
                code=getattr(permission, \'code\', None)
            )

权限在get_permissions方法中获取到,源码如下:

def get_permissions(self):
      """
      实例化并返回此视图所需的权限列表。
      """
      return [permission() for permission in self.permission_classes]

permission_classes又等于api_settings.DEFAULT_PERMISSION_CLASSES,所以我们去settings.py文件中查找

\'DEFAULT_PERMISSION_CLASSES\': [
        \'rest_framework.permissions.AllowAny\',
    ],

我们会发现drf默认的权限是AllowAny,我们去看下源码:

class AllowAny(BasePermission):
    """
    允许任意访问。这不是严格要求的,因为您可以使用空的 permission_classes 列表,但它很有用,因为它使意图更加明确。
    """

    def has_permission(self, request, view):
        return True

我们可以看到AllowAny继承自BasePermission,然后定义了has_permission方法,返回值为True

drf为我们提供了4个系统权限认证:

1. AllowAny
认证规则全部返回True:`return True`
    游客与登录用户都有所有权限

2. IsAuthenticated
认证规则必须有登录的合法用户:`return bool(request.user and request.user.is_authenticated)`
    游客没有任何权限,登录用户才有权限

3. IsAdminUser
认证规则必须是:`return bool(request.user and request.user.is_staff)`
    游客没有任何权限,登录用户才有权限

4. IsAuthenticatedOrReadOnly
    认证规则必须是只读请求或者是合法用户无限制
        return bool(
            request.method in SAFE_METHODS or
            request.user and
            request.user.is_authenticated
        )
        游客只读,合法用户无限制

 

自定义认证类

  1. 创建继承BasePermission的权限类
  2. 实现has_permission方法
  3. 实现体根据权限规则 确定 有无权限
  4. 进行全局或局部配置(一般采用局部配置)

权限规则
满足设置的用户条件,代表有权限,返回True
不满足设置的用户条件,代表有权限,返回False

自定义权限

from django.contrib.auth.models import Group
from rest_framework.permissions import BasePermission


class MyPermissions(BasePermission):
    def has_permission(self, request, view):
        rule1 = request.method in [\'GET\', \'OPTIONS\', \'HEAD\']
        group = Group.objects.filter(name="管理员").first()
        groups = request.user.groups.all()
        rule2 = group in groups
        rule3 = group and groups
        return rule1 or (rule2 and rule3)

以上定义了3条规则

  • rule1:请求方法是GETOPTIONSHEAD游客和用户都可以访问
  • rule2:当前用户如果有多个分组,其中必须有一个分组是管理员
  • rule3:管理员分组必须存在,用户必须在分组中

接下里我们定义视图

class TestView(APIView):
    permission_classes = [MyPermissions]

    def get(self, request, *args, **kwargs):
        print(request.user)
        return APIResponse(data_msg="所有用户都可以访问")

    def post(self, request, *args, **kwargs):
        print(request.user)
        return APIResponse(data_msg="只有管理员用户可以访问")

视图中只是添加了permission_classes = [MyPermissions]属与局部配置,也就是自定义的权限只针对此视图,其他视图还是默认的全局配置,如果我们还有其他的关于权限的需求,只需要在自定义的权限类中写逻辑即可

以上是关于从数据库查询权限信息与自定义失败处理的主要内容,如果未能解决你的问题,请参考以下文章

Django(63)drf权限源码分析与自定义权限

joomla 1.5 搜索与自定义组件的集成?

如何在 Data Studio 数据源列表中轻松找到直接 BigQuery 表数据源与自定义查询数据源?

ASP.NET Core 身份中的角色声明与自定义身份验证中的角色权限相比

Codeigniter 活动记录查询的工作方式与自定义查询不同

将 paypal 按钮与自定义表单集成,并在单击该按钮时将客户信息保存到数据库中