广告
返回顶部
首页 > 资讯 > 后端开发 > Python >spring security 自定义Provider 如何实现多种认证
  • 166
分享到

spring security 自定义Provider 如何实现多种认证

2024-04-02 19:04:59 166人浏览 薄情痞子

Python 官方文档:入门教程 => 点击学习

摘要

目录security内部认证流程是这样的1、 Controller2、spring security3、调用匹配的provider内部认证逻辑4、UserDetailsService

我的系统里有两种用户,对应数据库两张表,所以必须自定义provider 和 AuthenticationToken,这样才能走到匹配自定义的UserDetailsService。

必须自定义原因在于,security内部是遍历prodvider,根据其support 方法判断是否匹配Controller提交的token,然后走provider注入的认证service方法。

security内部认证流程是这样的

1、 Controller

用用户名和密码构造AuthenticationToken 并提交给 authenticationManager,


authenticationManager.authenticate(new UsernamePassWordAuthenticationToken(username, password));

2、spring security

会遍历自定义和内置provider,根据provider的support方法判断入参Token所匹配provider


public boolean supports(Class<?> authentication) {
   return (EcStaffUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

3、调用匹配的provider内部认证逻辑

过程中会调用UserDetailsService.loadUserByUsername,这个service可以在SecurityConfig中配置注入到provider

4、UserDetailsService

需要我们自己查询数据库中用户对象,返回对象UserDetails,

我返回的是LoginUser ( implements UserDetails ),这样把数据库查出来用户对象加进去,方便前台Controller使用


@Override
public UserDetails loadUserByUsername(String username) //查询数据库

5、继续走spring security内部逻辑

包括判断密码是否匹配等,如果密码不匹配或帐号过期等spring会上抛异常到Controller

6、所有调用完毕就会

回到Controller的方法,并返回authentication。对于异常需要自己捕获,详情可参见后面的代码。


authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
LoginUser loginUser = (LoginUser) authentication.getPrincipal();

说明:

大部分人是在流程最前面使用filter实现各种校验,而我的项目全部是前后端分离,所以我的filter只校验token有效性,我把各种非空校验放在controller。

1、基础配置-SecurityConfig


    @Autowired
    @Qualifier("userDetailsServiceImpl")
    private UserDetailsService userDetailsService;
    
    @Autowired
    @Qualifier("ecStaffDetailsServiceImpl")
    private UserDetailsService ecStaffDetailsServiceImpl;
 
    
    @Autowired
    private JwtAuthenticationTokenFilter authenticationTokenFilter;
    
    
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception
    {
        return super.authenticationManagerBean();
    }
 
    
    @Override
    protected void configure(httpsecurity HttpSecurity) throws Exception
    {
        httpSecurity
                // CRSF禁用,因为不使用session
                .csrf().disable()
                // 认证失败处理类
                .exceptionHandling().authenticationEntryPoint(unauthorizedHandler).and()
                // 基于token,所以不需要session
                .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
                // 过滤请求
                .authorizeRequests()
                // 对于登录login 验证码captchaImage 允许匿名访问
                .antMatchers("/login", "/captchaImage", "/store-api/ecommerce/login*.html",
                        "*.CSS",
                        "*.js"
                ).permitAll()
                .antMatchers("/profileapi-docs").anonymous()
                .antMatchers("/druid
    @Bean
    public BCryptPasswordEncoder bCryptPasswordEncoder()
    {
        return new BCryptPasswordEncoder();
    }
 
    
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception
    {
		//自定义provider及service,一套身份认证
        auth.authenticationProvider(getEcStaffUsernamePasswordAuthenticationProvider())
		//使用系统自带provider,及自定义service,另一套认证
            .userDetailsService(userDetailsService).passwordEncoder(bCryptPasswordEncoder());
    }    
	 
    public EcStaffUsernamePasswordAuthenticationProvider getEcStaffUsernamePasswordAuthenticationProvider() {
        EcStaffUsernamePasswordAuthenticationProvider provider = new EcStaffUsernamePasswordAuthenticationProvider();
        provider.setPasswordEncoder(bCryptPasswordEncoder());
        provider.setUserDetailsService(ecStaffDetailsServiceImpl);
        return provider;
    }

2、基础配置-自定义AuthenticationToken


import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
public class EcStaffUsernamePasswordAuthenticationToken extends UsernamePasswordAuthenticationToken{
    public EcStaffUsernamePasswordAuthenticationToken(Object principal, Object credentials) {
        super(principal, credentials);
    }
    private static final long serialVersionUID = 8665690993060353849L;   
}

3、基础配置-自定义provider


import org.springframework.security.authentication.dao.DaoAuthenticationProvider; 
import com.ruoyi.framework.security.authToken.EcStaffUsernamePasswordAuthenticationToken;
public class EcStaffUsernamePasswordAuthenticationProvider extends DaoAuthenticationProvider{
    public boolean supports(Class<?> authentication) {
        return (EcStaffUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

4、Controller发起身份认证


        // 用户验证
        Authentication authentication = null;
        try
        {
            // 该方法会去调用EcStaffDetailsServiceImpl.loadUserByUsername
            // 因为这个自定token只被自定provider的support所支持
            // 所以才会provider中注入的EcStaffDetailsServiceImpl,在security配置文件注入的
            authentication = authenticationManager.authenticate(new EcStaffUsernamePasswordAuthenticationToken(username, password));
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {        
                //密码不匹配,需自定义返回前台消息
                throw new UserPasswordNotMatchException();
            }
            else
            {
                throw new CustomException(e.getMessage());
            }
        }        
        //登录成功
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();

5、service查询数据库中用户对象


import java.util.HashSet;
import java.util.Set; 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.stereotype.Service;
 
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.ruoyi.common.constant.Constants;
import com.ruoyi.common.exception.BaseException;
import com.ruoyi.common.utils.MessageUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.ecommerce.constant.StaffStatusConstant;
import com.ruoyi.ecommerce.domain.EcStaff;
import com.ruoyi.ecommerce.service.IEcStaffService;
import com.ruoyi.framework.security.LoginUser;
 

@Service
public class EcStaffDetailsServiceImpl implements UserDetailsService
{
    private static final Logger log = LoggerFactory.getLogger(EcStaffDetailsServiceImpl.class); 
    @Autowired
    private IEcStaffService ecStaffService; 
    @Autowired
    private SysPermissionService permissionService; 
    @Override
    public UserDetails loadUserByUsername(String username)
    {
        QueryWrapper<EcStaff> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("phone", username);
        EcStaff user = ecStaffService.getOne(queryWrapper);
        
        if (StringUtils.isNull(user))
        {
            log.info("登录用户:{} 不存在.", username);
            throw new BaseException(MessageUtils.message("user.not.exists"));
        }
        else if (Constants.DELETED.equals(user.getDeleted()))
        {
            log.info("登录用户:{} 已被删除.", username);
            throw new BaseException(MessageUtils.message("user.password.delete"));
        }
        return createLoginUser(user);
    }
 
    
    public UserDetails createLoginUser(EcStaff user)
    {
        return new LoginUser(user, permissionService.getMenuPermission(user));        
    }
}

6、service返回的LoginUser

因为有两种用户sysuser和ecstaff,为了基于这个LoginUser统一提供getUsername方法,让他们继承或实现统一BaseUser,

可以不统一封装因为LoginUser构造方法入参是object , 即LoginUser(Object user, Set<String> permissions)


import java.util.Collection;
import java.util.Set; 
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails; 
import com.fasterxml.jackson.annotation.JSONIgnore;
import com.ruoyi.ecommerce.domain.BaseUser;
 

public class LoginUser implements UserDetails
{
    private static final long serialVersionUID = 1L;
 
    
    private String token;
 
    
    private Long loginTime;
 
    
    private Long expireTime;
 
    
    private String ipaddr;
 
    
    private String loginLocation;
 
    
    private String browser;
 
    
    private String os;
 
    
    private Set<String> permissions;
 
    
    private Object user;
    
    private Class userClass;
 
    public String getToken()
    {
        return token;
    }
 
    public void setToken(String token)
    {
        this.token = token;
    }
 
    public LoginUser()
    {
    }
 
    public LoginUser(Object user, Set<String> permissions)
    {
        this.userClass = user.getClass();
        this.user = user;
        this.permissions = permissions;
    }
 
    @JsonIgnore
    @Override
    public String getPassword()
    {
        return ((BaseUser)user).getPassword();
    }
 
    @Override
    public String getUsername()
    {
        return ((BaseUser)user).getUserName();
    }
 
    
    @JsonIgnore
    @Override
    public boolean isAccountNonExpired()
    {
        return true;
    }
 
    
    @JsonIgnore
    @Override
    public boolean isAccountNonLocked()
    {
        return true;
    }
 
    
    @JsonIgnore
    @Override
    public boolean isCredentialsNonExpired()
    {
        return true;
    }
 
    
    @JsonIgnore
    @Override
    public boolean isEnabled()
    {
        return true;
    }
 
    public Long getLoginTime()
    {
        return loginTime;
    }
 
    public void setLoginTime(Long loginTime)
    {
        this.loginTime = loginTime;
    }
 
    public String getIpaddr()
    {
        return ipaddr;
    }
 
    public void setIpaddr(String ipaddr)
    {
        this.ipaddr = ipaddr;
    }
 
    public String getLoginLocation()
    {
        return loginLocation;
    }
 
    public void setLoginLocation(String loginLocation)
    {
        this.loginLocation = loginLocation;
    }
 
    public String getBrowser()
    {
        return browser;
    }
 
    public void setBrowser(String browser)
    {
        this.browser = browser;
    }
 
    public String getOs()
    {
        return os;
    }
 
    public void setOs(String os)
    {
        this.os = os;
    }
 
    public Long getExpireTime()
    {
        return expireTime;
    }
 
    public void setExpireTime(Long expireTime)
    {
        this.expireTime = expireTime;
    }
 
    public Set<String> getPermissions()
    {
        return permissions;
    }
 
    public void setPermissions(Set<String> permissions)
    {
        this.permissions = permissions;
    }
 
    public Object getUser()
    {
        return user;
    }
 
    public void setUser(Object user)
    {
        this.user = user;
    }
 
    public Class getUserClass() {
        return userClass;
    }
 
    public void setUserClass(Class userClass) {
        this.userClass = userClass;
    }
 
    @Override
    public Collection<? extends GrantedAuthority> getAuthorities()
    {
        return null;
    }
}

7、另一套用户controller登录认证方法

注意这里换了security提供的AuthToken,这个token会调用security内部的DaoAuthenticationProvider进行认证


        // 用户验证
        Authentication authentication = null;
        try
        {
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            // 该方式使用的security内置token会使用内置DaoAuthenticationProvider认证
            // UserDetailsServiceImpl是在security config中配置的
            authentication = authenticationManager
                    .authenticate(new UsernamePasswordAuthenticationToken(username, password));
        }
        catch (Exception e)
        {
            if (e instanceof BadCredentialsException)
            {               
                throw new UserPasswordNotMatchException();
            }
            else
            {                
                throw new CustomException(e.getMessage());
            }
        }       
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();// 该方法会去调用

8、另一套用户service

可参照上述service写,查询另一张用户表即可,返回UserDetails

以上为个人经验,希望能给大家一个参考,也希望大家多多支持编程网。

--结束END--

本文标题: spring security 自定义Provider 如何实现多种认证

本文链接: https://www.lsjlt.com/news/160319.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • spring security 自定义Provider 如何实现多种认证
    目录security内部认证流程是这样的1、 Controller2、spring security3、调用匹配的provider内部认证逻辑4、UserDetailsService...
    99+
    2022-11-12
  • Spring Security自定义认证逻辑实例详解
    目录前言分析问题自定义 Authentication自定义 Filter自定义 Provider自定义认证成功/失败后的 Handler配置自定义认证的逻辑测试总结前言 这篇文章的内...
    99+
    2022-11-13
  • Spring Security如何实现HTTP认证
    今天小编给大家分享一下Spring Security如何实现HTTP认证的相关知识点,内容详细,逻辑清晰,相信大部分人都还太了解这方面的知识,所以分享这篇文章给大家参考一下,希望大家阅读完这篇文章后有所收获,下面我们一起来了解一下...
    99+
    2023-06-30
  • Spring Security基于自定义的认证提供器实现图形验证码流程解析
    目录前言一. 认证提供器简介1. 认证提供器AuthenticationProver2. WebAuthenticationDetails类介绍二. 实现图形验证码1. 添加依赖包2...
    99+
    2022-11-12
  • Spring Security OAuth2.0(五)-----OAuth2实现自定义统一认证登录页/自定义授权页/基于mysql存储数据
    本次实例涉及三个项目 核心项目工程unify_authorization_server(认证授权登录) 资源服务器项目unify_resource_server 测试项目是前面几篇写的项目 这里没有改...
    99+
    2023-09-11
    spring mysql java
  • Django通过自定义认证后端实现多种登录方式验证
    目录前言实现自定义认证后端前言 我们见到几乎所有的 Web 网站或者手机 App 也好,它们的最终目的都是要留住用户,提升自己网站的用户注册量,所以说用户的概念也必须深入到每一个程序...
    99+
    2022-11-12
  • .Net Core中如何写自定义认证实现
    今天就跟大家聊聊有关.Net Core中如何写自定义认证实现,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。一、起因 最近项目中需要对项目同时支持JWT认证,以及自定义的认证...
    99+
    2023-06-28
  • Spring Data Jpa如何实现自定义方法
    这篇文章将为大家详细讲解有关Spring Data Jpa如何实现自定义方法,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。Spring Data Jpa 自定义方法的实现最近项目中用到...
    99+
    2023-06-22
  • spring boot如何实现自定义配置源
    这篇文章给大家分享的是有关spring boot如何实现自定义配置源的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述我们知道,在Spring boot中可以通过xml或者@ImportResource 来引入自...
    99+
    2023-05-30
    springboot
  • Spring开发中如何实现自定义标签
    今天就跟大家聊聊有关Spring开发中如何实现自定义标签,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。Spring框架是现在Java最流行的开源框架之一,并且Spring下的各种子项...
    99+
    2023-05-31
    spring 标签
  • Spring Cloud Zuul如何实现自定义过滤器
    小编给大家分享一下Spring Cloud Zuul如何实现自定义过滤器,希望大家阅读完这篇文章之后都有所收获,下面让我们一起去探讨吧!构建Zuul自定义过滤器,限制ip频繁请求自定义zuul过滤器其实很简单1. 首先pom文件得先引入zu...
    99+
    2023-06-14
  • Spring Data MongoDB中如何实现自定义级联
    这篇文章给大家分享的是有关Spring Data MongoDB中如何实现自定义级联的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。前言Spring Data MongoDB 项目提供与MongoDB文档数据库的集...
    99+
    2023-05-30
    springdata mongodb
  • 使用Spring AOP 如何实现自定义注解
    这期内容当中小编将会给大家带来有关使用Spring AOP 如何实现自定义注解,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。在Maven中加入以下以依赖:<!-- Spring AOP + Aspe...
    99+
    2023-05-31
    springaop 注解
  • Spring Data Jpa多表查询如何返回自定义实体
    小编给大家分享一下Spring Data Jpa多表查询如何返回自定义实体,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!SpringDataJ...
    99+
    2023-06-29
  • 在Spring Boot项目中如何实现自定义PropertySourceLoader
    今天就跟大家聊聊有关在Spring Boot项目中如何实现自定义PropertySourceLoader,可能很多人都不太了解,为了让大家更加了解,小编给大家总结了以下内容,希望大家根据这篇文章可以有所收获。SpringBoot 的配置文件...
    99+
    2023-05-31
    propertysourceloader springboot ce
  • spring data jpa如何使用自定义repository实现类
    目录spring data jpa使用自定义repository实现类创建MyJpaRepository实现类创建MyJpaRepositoryFactoryBean配置JPAJpa...
    99+
    2022-11-12
  • 在spring-boot项目中如何实现自定义filter
    在spring-boot项目中如何实现自定义filter?很多新手对此不是很清楚,为了帮助大家解决这个难题,下面小编将为大家详细讲解,有这方面需求的人可以来学习下,希望你能有所收获。传统的javaEE增加Filter是在web.xml中配置...
    99+
    2023-05-31
    springboot 自定义 filter
  • DreamWeaver CS3中如何实现SPRY的自定义验证
    这篇文章将为大家详细讲解有关DreamWeaver CS3中如何实现SPRY的自定义验证,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。代码如下:<span id="sprytextfiel...
    99+
    2023-06-08
  • vue elementUI如何实现自定义正则规则验证
    这篇文章给大家分享的是有关vue elementUI如何实现自定义正则规则验证的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。项目场景:常见的表单填写中都会遇到,比如新增信息,修改信息等,如下图相信大家对...
    99+
    2023-06-29
  • 使用Spring Data JPA如何实现自定义规则查询
    使用Spring Data JPA如何实现自定义规则查询?针对这个问题,这篇文章详细介绍了相对应的分析和解答,希望可以帮助更多想解决这个问题的小伙伴找到更简单易行的方法。一、常用规则速查1   And    并且2   Or    &nbs...
    99+
    2023-05-31
    spring data jpa
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作