SpringSecurity多認證器配置多模式登錄自定義認證器方式
首先說下項目使用背景
A服務(wù) 和B服務(wù) 都在項目中 認證服務(wù)是一個公共模塊 需要多個認證器
第一步
我們先說說 WebSecurityConfigBugVip.class
/** * @Author: Mr_xk * @Description: 配置類 * @Date: 2021/8/1 **/ @Configuration @EnableWebSecurity public class WebSecurityConfigBugVip extends WebSecurityConfigurerAdapter { //jwt生成 token 和續(xù)期的類 private TokenManager tokenManager; // 操作redis private RedisTemplate redisTemplate; @Autowired //租戶服務(wù)的認證器 private TenantDetailsAuthenticationProvider userDetailsAuthenticationProvider; @Autowired //平臺端的認證器 private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider; @Autowired @Qualifier("authenticationManagerBean")//認證管理器 private AuthenticationManager authenticationManager; /** * 裝配自定義的Provider * @param auth */ @Override public void configure(AuthenticationManagerBuilder auth){ //這個為BOSS認證器 (也可以理解為上圖A服務(wù)) auth.authenticationProvider(userDetailsAuthenticationProvider); //這個為Tenant認證器 (B服務(wù)) auth.authenticationProvider(usernamePasswordAuthenticationProvider); } /** * * 注入 RedisTemplate * */ @Bean public RedisTemplate redisTemplateInit() { //設(shè)置序列化Key的實例化對象 redisTemplate.setKeySerializer(new StringRedisSerializer()); //設(shè)置序列化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; } /** * 配置設(shè)置 * * @param http * @throws Exception */ @Override protected void configure(HttpSecurity http) throws Exception { http.exceptionHandling() .authenticationEntryPoint(new UnauthorizedEntryPoint())//未授權(quán)的統(tǒng)一處理類 .and().csrf().disable()//跨域請求處理我在網(wǎng)管那邊處理了所以這里不做處理 .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()//退出登錄的邏輯處理類 實現(xiàn)的是LogoutHandler接口 .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();//設(shè)置訪問過濾器 } /** * *登錄過濾器 */ @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() { //注入的時候設(shè)置 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)); } } /** * 設(shè)置在線用戶 * @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 服務(wù)使用的認證器
@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); //這里是設(shè)置登錄日志 asyncFactory.loginLogSet(username, LogConstant.LOGIN_SUCCESS,LogConstant.LOGIN_LOG ,LogConstant.LOG_INFO,LogConstant.LOGIN_BOSS,""); return result; }else{ //這里是設(shè)置登錄日志 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; } }
本次項目中 租戶和平臺端 服務(wù)都要走 公共模塊SpringSecurity 去認證。自定義認證器可以繼續(xù)加(業(yè)務(wù)場景需要的時候)
下面是我的SpringSecurity 公共模塊
感興趣的可以嘗試下。。。多認證器
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java調(diào)用基于Ollama本地大模型的實現(xiàn)
本文主要介紹了Java調(diào)用基于Ollama本地大模型的實現(xiàn),實現(xiàn)文本生成、問答、文本分類等功能,開發(fā)者可以輕松配置和調(diào)用模型,具有一定的參考價值,感興趣的可以了解一下2025-03-03Java使用設(shè)計模式中迭代器模式構(gòu)建項目的代碼結(jié)構(gòu)示例
這篇文章主要介紹了Java使用設(shè)計模式中迭代器模式構(gòu)建項目的代碼結(jié)構(gòu)示例,迭代器模式能夠?qū)υL問者隱藏對象的內(nèi)部細節(jié),需要的朋友可以參考下2016-05-05解決使用RestTemplate時報錯RestClientException的問題
這篇文章主要介紹了解決使用RestTemplate時報錯RestClientException的問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-08-08JavaWeb請求轉(zhuǎn)發(fā)和請求包含實現(xiàn)過程解析
這篇文章主要介紹了JavaWeb請求轉(zhuǎn)發(fā)和請求包含實現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2020-02-02