SpringSecurity多认证器配置多模式登录自定义认证器方式

 更新时间:2025年04月06日 11:09:54   作者:青衣画白扇  
这篇文章主要介绍了SpringSecurity多认证器配置多模式登录自定义认证器方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教

首先说下项目使用背景

A服务 和B服务 都在项目中 认证服务是一个公共模块 需要多个认证器

第一步

我们先说说 WebSecurityConfigBugVip.class

/**
 * @Author:  Mr_xk
 * @Description: 配置类
 * @Date:  2021/8/1
 **/
@Configuration
@EnableWebSecurity
public class WebSecurityConfigBugVip extends WebSecurityConfigurerAdapter {
	//jwt生成 token 和续期的类
    private TokenManager tokenManager;
    // 操作redis 
    private RedisTemplate redisTemplate;
    @Autowired
    //租户服务的认证器
    private TenantDetailsAuthenticationProvider userDetailsAuthenticationProvider;
    
    @Autowired
    //平台端的认证器
    private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider;
    @Autowired
    @Qualifier("authenticationManagerBean")//认证管理器
    private AuthenticationManager authenticationManager;

    /**
     * 装配自定义的Provider
     * @param auth
     */
    @Override
    public void configure(AuthenticationManagerBuilder auth){
    	//这个为BOSS认证器 (也可以理解为上图A服务)
        auth.authenticationProvider(userDetailsAuthenticationProvider);
        //这个为Tenant认证器 (B服务)
        auth.authenticationProvider(usernamePasswordAuthenticationProvider);
    }

	/**
	  *
	  * 注入 RedisTemplate 
	  * 
	  */	
    @Bean
    public RedisTemplate redisTemplateInit() {
        //设置序列化Key的实例化对象
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        //设置序列化Value的实例化对象
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
        return redisTemplate;
    }

    @Autowired
    public WebSecurityConfigBugVip(TokenManager tokenManager, RedisTemplate redisTemplate) {
        this.tokenManager = tokenManager;
        this.redisTemplate = redisTemplate;
    }
    /**
     * 配置设置
     *
     * @param http
     * @throws Exception
     */
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.exceptionHandling()
                .authenticationEntryPoint(new UnauthorizedEntryPoint())//未授权的统一处理类
                .and().csrf().disable()//跨域请求处理我在网管那边处理了所以这里不做处理
                .addFilterAt(tokenLoginFilter(), UsernamePasswordAuthenticationFilter.class)//登录Filter
                .authorizeRequests()//配置需要放行的请求
                .antMatchers("/boss/verifi/getCode").permitAll()
                .antMatchers("/boss/verifi/checkVrrifyCode").permitAll()
                .antMatchers("/swagger-resources/**").permitAll()
                .antMatchers("/webjars/**").permitAll()
                .antMatchers("/v2/**").permitAll()
                .antMatchers("/swagger-ui.html/**").permitAll()
                .anyRequest().authenticated()
                .and().logout().logoutUrl("/boss/acl/logout")//平台端退出
                .and().logout().logoutUrl("/admin/acl/logout")//租户段推出
                .addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate)).and()//退出登录的逻辑处理类  实现的是LogoutHandler接口
                .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();//设置访问过滤器
    }
    /**
      *
      *登录过滤器
      */
    @Bean
    public TokenLoginFilter tokenLoginFilter() {
        TokenLoginFilter filter = new TokenLoginFilter();
        filter.setAuthenticationManager(authenticationManager);
        return filter;
    }

    /**
     * 处理注入 AuthenticationManager失败问题
     * @return
     * @throws Exception
     */
    @Override
    @Bean
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
}

第二步

说一下 TokenLoginFilter.class 这个类中主要作用是 分派验证器

public class TokenLoginFilter extends AbstractAuthenticationProcessingFilter {
	//登录地址
    public TokenLoginFilter() {
    	//注入的时候设置
        super(new AntPathRequestMatcher("/bugVip/acl/login", "POST"));
    }
    @Autowired
    private TokenManager tokenManager;
    @Autowired
    private RedisCache redisTemplate;
    // 令牌有效期(默认30分钟)
    @Value("${token.expireTime}")
    private int expireTime;
    protected static final long MILLIS_SECOND = 1000;

    protected static final long MILLIS_MINUTE = 60 * MILLIS_SECOND;

    @Override
    public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
        if (!httpServletRequest.getMethod().equals("POST")) {
            throw new AuthenticationServiceException(
                    "Authentication method not supported: " + httpServletRequest.getMethod());
        }
        User user = new ObjectMapper().readValue( httpServletRequest.getInputStream(), User.class);
        //处理认证器
        AbstractAuthenticationToken authRequest = null;
        switch(user.getType()) {
            //租户登录
            case "1":
                authRequest = new TenantAuthenticationToken(user.getUsername(), user.getPassword());
                break;
            //平台登录
            case "2":
                authRequest = new UsernamePasswordAuthenticationToken(user.getUsername(), user.getPassword());
                break;
        }
        setDetails(httpServletRequest, authRequest);
        return this.getAuthenticationManager().authenticate(authRequest);
    }
    protected void setDetails(HttpServletRequest request,
                              AbstractAuthenticationToken authRequest) {
        authRequest.setDetails(authenticationDetailsSource.buildDetails(request));
    }

    /**
     * 登录成功
     * @param req
     * @param res
     * @param chain
     * @param auth
     * @throws IOException
     * @throws ServletException
     */
    @Override
    protected void successfulAuthentication(HttpServletRequest req, HttpServletResponse res, FilterChain chain,
                                            Authentication auth){
        String fastUUID = IdUtils.fastUUID();
        String datakey =(String)req.getAttribute("datakey");
        String token = tokenManager.createToken(fastUUID);
        Collection<? extends GrantedAuthority> principal = auth.getAuthorities();
        List<String> collect = principal.stream().map(e -> e.toString()).collect(Collectors.toList());
        redisTemplate.setCacheObject(RedisConstant.PERRMISSION+fastUUID,collect,expireTime, TimeUnit.MINUTES);
        OnlineUserInfo onlineUserInfo = setonlineUserInfo(token, auth,req,datakey);
        if(StringUtils.isEmpty(datakey)){
            redisTemplate.setCacheObject(RedisConstant.ONLINE_BOSS_INFO+fastUUID,onlineUserInfo,expireTime,TimeUnit.MINUTES);
            ResponseUtil.out(res, R.ok().data("token", token));
        }else{
            redisTemplate.setCacheObject(RedisConstant.ONLINE_INFO+fastUUID,onlineUserInfo,expireTime,TimeUnit.MINUTES);
            ResponseUtil.out(res, R.ok().data("token", token).data("datakey",datakey));
        }
    }
    /**
     * 设置在线用户
     * @param token
     * @param authentication
     * @param request
     * @return
     */
    public OnlineUserInfo setonlineUserInfo(String token, Authentication authentication, HttpServletRequest request,String datakey){
        OnlineUserInfo onlineUserInfo = new OnlineUserInfo();
        SecurityUser principal = (SecurityUser) authentication.getPrincipal();
        onlineUserInfo.setSysUser(principal.getSysUser());
        onlineUserInfo.setToken(token);
        onlineUserInfo.setDatakey(datakey);
        onlineUserInfo.setLoginTime(System.currentTimeMillis());
        onlineUserInfo.setExpireTime(System.currentTimeMillis()+expireTime * MILLIS_MINUTE);
        RequestWrapper requestWrapper = new RequestWrapper(request);
        onlineUserInfo.setIpaddr(requestWrapper.getRemoteAddr());
        IpData ipData = IpGetAdders.doPostOrGet();
        onlineUserInfo.setIpadderss(ipData.getCname());
        onlineUserInfo.setUserOs(UserAgentUtil.parse(requestWrapper.getHeader("User-Agent")).getOs().toString());
        onlineUserInfo.setBrowser(UserAgentUtil.parse(requestWrapper.getHeader("User-Agent")).getBrowser().toString());
        return onlineUserInfo;
    }
    /**
     * 登录失败
     * @param request
     * @param response
     * @param e
     */
    @Override
    protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
                                              AuthenticationException e) {
        ResponseUtil.out(response, R.error().data("message",e.getMessage()));
    }



}

我们先说一下第一个认证器

UsernamePasswordAuthenticationProvider.class Boos 服务使用的认证器

@Component
public class UsernamePasswordAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UserDetailsServicesBoss userDetailsServicesMy;
    @Autowired
    private AsyncFactory asyncFactory;
    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username =authentication.getName();
        String password = (String) authentication.getCredentials();
        DefaultPasswordEncoder passwordEncoder =new DefaultPasswordEncoder();
        SecurityUser userDetails = userDetailsServicesMy.loadUserByUsername(username);
        //密码比对
        if(passwordEncoder.encode(password).equals(userDetails.getPassword())){
            UsernamePasswordAuthenticationToken  result = new UsernamePasswordAuthenticationToken (userDetails, null, userDetails.getAuthorities());
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            RequestContextHolder.setRequestAttributes(requestAttributes,true);
            //这里是设置登录日志
            asyncFactory.loginLogSet(username, LogConstant.LOGIN_SUCCESS,LogConstant.LOGIN_LOG ,LogConstant.LOG_INFO,LogConstant.LOGIN_BOSS,"");
            return result;
        }else{
         //这里是设置登录日志
            asyncFactory.loginLogSet(username, LogConstant.LOGIN_FAIL,LogConstant.LOGIN_LOG ,LogConstant.LOG_INFO,LogConstant.LOGIN_BOSS,new BadCredentialsException("密码不正确").toString());
            throw new BadCredentialsException("密码不正确");
        }
    }

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

第二个认证器

TenantDetailsAuthenticationProvider.class 都是implements 接口 AuthenticationProvider

@Component
public class TenantDetailsAuthenticationProvider implements AuthenticationProvider {
    @Autowired
    private UserDetailServicesTenant userDetailServicesTenant;
    @Autowired
    private RedisCache redisTemplate;
    @Autowired
    private AsyncFactory asyncFactory;
    /**
     * 认证器
     * @param authentication
     * @return
     * @throws AuthenticationException
     */
    @SneakyThrows
    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = authentication.getName();
        String password = (String) authentication.getCredentials();
        HttpServletRequest request = ServletUtils.getRequest();
        RequestWrapper requestWrapper = new RequestWrapper(request);
        String ip = requestWrapper.getRemoteAddr();
        String datakey = redisTemplate.getCacheMapValue(RedisConstant.TENANT_DATAKEY,ip);
        requestWrapper.setAttribute("datakey",datakey);
        SecurityUser userDetails = userDetailServicesTenant.loadUserByUsername(username,datakey);
        DefaultPasswordEncoder passwordEncoder = new DefaultPasswordEncoder();
        boolean matches = passwordEncoder.matches(passwordEncoder.encode(password), userDetails.getPassword());
        if(matches){
            TenantAuthenticationToken result = new TenantAuthenticationToken(userDetails, "", userDetails.getAuthorities());
            result.setDetails(authentication.getDetails());
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            RequestContextHolder.setRequestAttributes(requestAttributes,true);

            asyncFactory.loginLogSet(username, LogConstant.LOGIN_SUCCESS,LogConstant.LOGIN_LOG ,LogConstant.LOG_INFO,LogConstant.LOGIN_TENANT,"");
            return result;
        }else{
            asyncFactory.loginLogSet(username, LogConstant.LOGIN_FAIL,LogConstant.LOGIN_LOG ,LogConstant.LOG_INFO,LogConstant.LOGIN_TENANT,new BadCredentialsException("密码不正确").toString());
            throw new BadCredentialsException("密码不正确");
        }
    }

    /**
     * 如果该AuthenticationProvider支持传入的Authentication对象,则返回true
     * @param authentication
     * @return
     */
    @Override
    public boolean supports(Class<?> authentication) {
        return (TenantAuthenticationToken.class.isAssignableFrom(authentication));
    }
}

最后再贴一下 services 的代码

//租户的services
@Service
public class UserDetailServicesTenant {
    @Autowired
    private TenantLoginServices tenantLoginServices;

    public SecurityUser loadUserByUsername(String name,String datakey) throws UsernameNotFoundException {
        SysUser tenantUser = tenantLoginServices.findbyTenantname(datakey,name);
        if (ObjectUtils.isEmpty(tenantUser)) {
            throw new UsernameNotFoundException("租户不存在");
        }
        User usersecurity = new User();
        BeanUtils.copyProperties(tenantUser, usersecurity);
        List<String> authorities= tenantLoginServices.selectPermissionValueByRolerTenant(datakey, tenantUser.getId());
        List<String> collect = authorities.stream().filter(e -> !ObjectUtils.isEmpty(e)).distinct().collect(Collectors.toList());
        collect.add("admin/acl/info");
        collect.add("admin/acl/getmenu");
        collect.add("admin/acl/logout");
        SecurityUser securityUser = new SecurityUser(usersecurity);
        securityUser.setPermissionValueList(collect);
        securityUser.setSysUser(tenantUser);
        securityUser.setLoginType(2);
        return securityUser;
    }
}
//Boss 的servics
@Service
public class UserDetailsServicesBoss {
    @Autowired
    private BossTenantServices loginServices;

    public SecurityUser loadUserByUsername(String name) throws UsernameNotFoundException {
        SysUser  user= loginServices.findbyname(name);
        if(StringUtils.isEmpty(user)){
            throw new UsernameNotFoundException("用户名不存在");
        }
        User usersecurity = new User();
        BeanUtils.copyProperties(user,usersecurity);
        List<String>  authorities = loginServices.selectPermissionValueByRoler(user.getSysUserRolerId());
        List<String> collect = authorities.stream().filter(e-> !ObjectUtils.isEmpty(e)).distinct().collect(Collectors.toList());
        collect.add("boss/acl/info");
        collect.add("boss/acl/getmenu");
        collect.add("boss/acl/logout");
        SecurityUser securityUser = new SecurityUser(usersecurity);
        securityUser.setPermissionValueList(collect);
        securityUser.setSysUser(user);
        securityUser.setLoginType(1);
        return securityUser;
    }
}

本次项目中 租户和平台端 服务都要走 公共模块SpringSecurity 去认证。自定义认证器可以继续加(业务场景需要的时候)

下面是我的SpringSecurity 公共模块

感兴趣的可以尝试下。。。多认证器

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • Java后台返回blob格式的文件流的解决方案

    Java后台返回blob格式的文件流的解决方案

    在Java后台开发中,经常会遇到需要返回Blob格式的文件流给前端的情况,Blob是一种二进制大对象类型,可以用于存储大量的二进制数据,例如图片、音频、视频等,本文将为你详细介绍如何在Java后台中返回Blob格式的文件流,需要的朋友可以参考下
    2024-08-08
  • Java调用基于Ollama本地大模型的实现

    Java调用基于Ollama本地大模型的实现

    本文主要介绍了Java调用基于Ollama本地大模型的实现,实现文本生成、问答、文本分类等功能,开发者可以轻松配置和调用模型,具有一定的参考价值,感兴趣的可以了解一下
    2025-03-03
  • Spring Boot如何利用拦截器加缓存完成接口防刷操作

    Spring Boot如何利用拦截器加缓存完成接口防刷操作

    流的需求出现在许多常见的场景中,下面这篇文章主要给大家介绍了关于Spring Boot如何利用拦截器加缓存完成接口防刷操作的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-02-02
  • Java9中操作和查询本地进程信息的示例详解

    Java9中操作和查询本地进程信息的示例详解

    这篇文章主要为大家详细介绍了Java9中操作和查询本地进程信息的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03
  • Java使用设计模式中迭代器模式构建项目的代码结构示例

    Java使用设计模式中迭代器模式构建项目的代码结构示例

    这篇文章主要介绍了Java使用设计模式中迭代器模式构建项目的代码结构示例,迭代器模式能够对访问者隐藏对象的内部细节,需要的朋友可以参考下
    2016-05-05
  • 深入了解Java atomic原子类的使用方法和原理

    深入了解Java atomic原子类的使用方法和原理

    这篇文章主要介绍了深入了解Java atomic原子类的使用方法和原理,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,,需要的朋友可以参考下
    2019-06-06
  • jpanel设置背景图片的二个小例子

    jpanel设置背景图片的二个小例子

    这篇文章主要介绍了jpanel设置背景图片的二个小例子,实现了动态加载图片做背景的方法,需要的朋友可以参考下
    2014-03-03
  • 解决使用RestTemplate时报错RestClientException的问题

    解决使用RestTemplate时报错RestClientException的问题

    这篇文章主要介绍了解决使用RestTemplate时报错RestClientException的问题,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-08-08
  • JavaWeb请求转发和请求包含实现过程解析

    JavaWeb请求转发和请求包含实现过程解析

    这篇文章主要介绍了JavaWeb请求转发和请求包含实现过程解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-02-02
  • Java设计模式中单一职责原则详解

    Java设计模式中单一职责原则详解

    这篇文章主要介绍了Java设计模式中单一职责原则详解,单一职责原则 (SRP) 是软件设计中的一个重要原则,它要求每个类只负责一个职责,需要的朋友可以参考下
    2023-05-05

最新评论