首先说下项目使用背景
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 公共模块
感兴趣的可以尝试下。。。多认证器