SpringSecurity多认证器配置多模式登录自定义认证器方式
作者:青衣画白扇
这篇文章主要介绍了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 公共模块
感兴趣的可以尝试下。。。多认证器
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。