SpringSecurity多認(rèn)證器配置多模式登錄自定義認(rèn)證器方式
首先說下項(xiàng)目使用背景
A服務(wù) 和B服務(wù) 都在項(xiàng)目中 認(rèn)證服務(wù)是一個(gè)公共模塊 需要多個(gè)認(rèn)證器

第一步
我們先說說 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ù)的認(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è)為Tenant認(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)一處理類
.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")//租戶段推出
.addLogoutHandler(new TokenLogoutHandler(tokenManager, redisTemplate)).and()//退出登錄的邏輯處理類 實(shí)現(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 這個(gè)類中主要作用是 分派驗(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()) {
//租戶登錄
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è)置在線用戶
* @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()));
}
}
我們先說一下第一個(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 的代碼
//租戶的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;
}
}本次項(xiàng)目中 租戶和平臺(tái)端 服務(wù)都要走 公共模塊SpringSecurity 去認(rèn)證。自定義認(rèn)證器可以繼續(xù)加(業(yè)務(wù)場景需要的時(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)文本生成、問答、文本分類等功能,開發(fā)者可以輕松配置和調(diào)用模型,具有一定的參考價(jià)值,感興趣的可以了解一下2025-03-03
Java使用設(shè)計(jì)模式中迭代器模式構(gòu)建項(xiàng)目的代碼結(jié)構(gòu)示例
這篇文章主要介紹了Java使用設(shè)計(jì)模式中迭代器模式構(gòu)建項(xiàng)目的代碼結(jié)構(gòu)示例,迭代器模式能夠?qū)υL問者隱藏對(duì)象的內(nèi)部細(xì)節(jié),需要的朋友可以參考下2016-05-05
解決使用RestTemplate時(shí)報(bào)錯(cuò)RestClientException的問題
這篇文章主要介紹了解決使用RestTemplate時(shí)報(bào)錯(cuò)RestClientException的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08
JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過程解析
這篇文章主要介紹了JavaWeb請(qǐng)求轉(zhuǎn)發(fā)和請(qǐng)求包含實(shí)現(xiàn)過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-02-02
Java設(shè)計(jì)模式中單一職責(zé)原則詳解
這篇文章主要介紹了Java設(shè)計(jì)模式中單一職責(zé)原則詳解,單一職責(zé)原則 (SRP) 是軟件設(shè)計(jì)中的一個(gè)重要原則,它要求每個(gè)類只負(fù)責(zé)一個(gè)職責(zé),需要的朋友可以參考下2023-05-05

