SpringSecurity多認(rèn)證器配置多模式登錄自定義認(rèn)證器方式
首先說(shuō)下項(xiàng)目使用背景
A服務(wù) 和B服務(wù) 都在項(xiàng)目中 認(rèn)證服務(wù)是一個(gè)公共模塊 需要多個(gè)認(rèn)證器
第一步
我們先說(shuō)說(shuō) WebSecurityConfigBugVip.class
/** * @Author: Mr_xk * @Description: 配置類(lèi) * @Date: 2021/8/1 **/ @Configuration @EnableWebSecurity public class WebSecurityConfigBugVip extends WebSecurityConfigurerAdapter { //jwt生成 token 和續(xù)期的類(lèi) private TokenManager tokenManager; // 操作redis private RedisTemplate redisTemplate; @Autowired //租戶(hù)服務(wù)的認(rèn)證器 private TenantDetailsAuthenticationProvider userDetailsAuthenticationProvider; @Autowired //平臺(tái)端的認(rèn)證器 private UsernamePasswordAuthenticationProvider usernamePasswordAuthenticationProvider; @Autowired @Qualifier("authenticationManagerBean")//認(rèn)證管理器 private AuthenticationManager authenticationManager; /** * 裝配自定義的Provider * @param auth */ @Override public void configure(AuthenticationManagerBuilder auth){ //這個(gè)為BOSS認(rèn)證器 (也可以理解為上圖A服務(wù)) auth.authenticationProvider(userDetailsAuthenticationProvider); //這個(gè)為T(mén)enant認(rèn)證器 (B服務(wù)) auth.authenticationProvider(usernamePasswordAuthenticationProvider); } /** * * 注入 RedisTemplate * */ @Bean public RedisTemplate redisTemplateInit() { //設(shè)置序列化Key的實(shí)例化對(duì)象 redisTemplate.setKeySerializer(new StringRedisSerializer()); //設(shè)置序列化Value的實(shí)例化對(duì)象 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)一處理類(lèi) .and().csrf().disable()//跨域請(qǐng)求處理我在網(wǎng)管那邊處理了所以這里不做處理 .addFilterAt(tokenLoginFilter(), UsernamePasswordAuthenticationFilter.class)//登錄Filter .authorizeRequests()//配置需要放行的請(qǐng)求 .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")//平臺(tái)端退出 .and().logout().logoutUrl("/admin/acl/logout")//租戶(hù)段推出 .addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate)).and()//退出登錄的邏輯處理類(lèi) 實(shí)現(xiàn)的是LogoutHandler接口 .addFilter(new TokenAuthenticationFilter(authenticationManager(), tokenManager, redisTemplate)).httpBasic();//設(shè)置訪問(wèn)過(guò)濾器 } /** * *登錄過(guò)濾器 */ @Bean public TokenLoginFilter tokenLoginFilter() { TokenLoginFilter filter = new TokenLoginFilter(); filter.setAuthenticationManager(authenticationManager); return filter; } /** * 處理注入 AuthenticationManager失敗問(wèn)題 * @return * @throws Exception */ @Override @Bean public AuthenticationManager authenticationManagerBean() throws Exception { return super.authenticationManagerBean(); } }
第二步
說(shuō)一下 TokenLoginFilter.class
這個(gè)類(lèi)中主要作用是 分派驗(yàn)證器
public class TokenLoginFilter extends AbstractAuthenticationProcessingFilter { //登錄地址 public TokenLoginFilter() { //注入的時(shí)候設(shè)置 super(new AntPathRequestMatcher("/bugVip/acl/login", "POST")); } @Autowired private TokenManager tokenManager; @Autowired private RedisCache redisTemplate; // 令牌有效期(默認(rèn)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); //處理認(rèn)證器 AbstractAuthenticationToken authRequest = null; switch(user.getType()) { //租戶(hù)登錄 case "1": authRequest = new TenantAuthenticationToken(user.getUsername(), user.getPassword()); break; //平臺(tái)登錄 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è)置在線用戶(hù) * @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())); } }
我們先說(shuō)一下第一個(gè)認(rèn)證器
UsernamePasswordAuthenticationProvider.class
Boos 服務(wù)使用的認(rèn)證器
@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); //密碼比對(duì) 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)); } }
第二個(gè)認(rèn)證器
TenantDetailsAuthenticationProvider.class
都是implements 接口 AuthenticationProvider
@Component public class TenantDetailsAuthenticationProvider implements AuthenticationProvider { @Autowired private UserDetailServicesTenant userDetailServicesTenant; @Autowired private RedisCache redisTemplate; @Autowired private AsyncFactory asyncFactory; /** * 認(rèn)證器 * @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對(duì)象,則返回true * @param authentication * @return */ @Override public boolean supports(Class<?> authentication) { return (TenantAuthenticationToken.class.isAssignableFrom(authentication)); } }
最后再貼一下 services 的代碼
//租戶(hù)的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("租戶(hù)不存在"); } 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("用戶(hù)名不存在"); } 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; } }
本次項(xiàng)目中 租戶(hù)和平臺(tái)端 服務(wù)都要走 公共模塊SpringSecurity 去認(rèn)證。自定義認(rèn)證器可以繼續(xù)加(業(yè)務(wù)場(chǎng)景需要的時(shí)候)
下面是我的SpringSecurity 公共模塊
感興趣的可以嘗試下。。。多認(rèn)證器
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java調(diào)用基于Ollama本地大模型的實(shí)現(xiàn)
本文主要介紹了Java調(diào)用基于Ollama本地大模型的實(shí)現(xiàn),實(shí)現(xiàn)文本生成、問(wèn)答、文本分類(lèi)等功能,開(kāi)發(fā)者可以輕松配置和調(diào)用模型,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03Java9中操作和查詢(xún)本地進(jìn)程信息的示例詳解
這篇文章主要為大家詳細(xì)介紹了Java9中操作和查詢(xún)本地進(jìn)程信息的相關(guān)知識(shí),文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03Java使用設(shè)計(jì)模式中迭代器模式構(gòu)建項(xiàng)目的代碼結(jié)構(gòu)示例
這篇文章主要介紹了Java使用設(shè)計(jì)模式中迭代器模式構(gòu)建項(xiàng)目的代碼結(jié)構(gòu)示例,迭代器模式能夠?qū)υL問(wèn)者隱藏對(duì)象的內(nèi)部細(xì)節(jié),需要的朋友可以參考下2016-05-05深入了解Java atomic原子類(lèi)的使用方法和原理
這篇文章主要介紹了深入了解Java atomic原子類(lèi)的使用方法和原理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,,需要的朋友可以參考下2019-06-06解決使用RestTemplate時(shí)報(bào)錯(cuò)RestClientException的問(wèn)題
這篇文章主要介紹了解決使用RestTemplate時(shí)報(bào)錯(cuò)RestClientException的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02Java設(shè)計(jì)模式中單一職責(zé)原則詳解
這篇文章主要介紹了Java設(shè)計(jì)模式中單一職責(zé)原則詳解,單一職責(zé)原則 (SRP) 是軟件設(shè)計(jì)中的一個(gè)重要原則,它要求每個(gè)類(lèi)只負(fù)責(zé)一個(gè)職責(zé),需要的朋友可以參考下2023-05-05