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 公共模块在这里插入图片描述
感兴趣的可以尝试下。。。多认证器

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值