-笔者在实际生产中,遇到需求:
- 给公司的5个系统统一加上token效验的需求,做成公共的jar包,
- 而且要求使用spring security,暂时不做url权限的控制
- 所有的请求进入到不同的系统,系统可以自由配置那些请求需要拦截,那些不需要拦截,
- 并且请求中的token在失效前都必须在统一登录系统(LDAP)刷新token,登录系统的token时长是2小时,其他系统统一设置成1小时,
**- - 首先是对spring security进行一个大致的了解
spring security包含大量的过滤器,抽象到晦涩难懂,核心配置类是WebSecurityConfigurerAdapter ,必须继承这个类,然后重写他的方法
**
@Configuration
@EnableWebSecurity
public class TokenAuthConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web.ignoring().requestMatchers(“**”);// 这个是你自己自定义 那些不需要拦截,不需要token
}
@Override
protected void configure(final HttpSecurity http) throws Exception {
http
.exceptionHandling()
.defaultAuthenticationEntryPointFor(forbiddenEntryPoint(), “url”) // ** 这个是 你自己配置那些需要进行验证token
.and()
.authenticationProvider(provider) // 这个provider就是你自定义提供的对token内对应的userinfo进行校验
.addFilterBefore(“filter”, AnonymousAuthenticationFilter.class)// 自定义的需要的拦截器
.authorizeRequests()
.requestMatchers(“url”)// ** 这个是自定义的需要拦截的url
.authenticated()
.and()
.csrf().disable() // 禁用跨站拦截
.formLogin().disable()// 不使用 自带的登录界面
.httpBasic().disable()
.logout().disable();
}
}
- 自定义的provider:
这里使用了自定义的 本地缓存策略,就是在一个类里面写一个全局的map,然后设置一下过期时间,过期就进行清除淘汰,然后还使用了 一个自定义的restTemplate
@Slf4j
final class TokenAuthProvider extends AbstractUserDetailsAuthenticationProvider {
@Value("${csf.request.appId}")
private String appId;
@Value("${csf.request.clientId}")
private String clientId;
@Value("${csf.request.clientSecret}")
private String clientSecret;
@Value("${csf.request.url}")
private String url;
@Resource
private LocalCache localCache;
@Resource
private TokenRestTemplate tokenRestTemplate;
@Override
protected void additionalAuthenticationChecks(final UserDetails d, final UsernamePasswordAuthenticationToken auth) {
}
@Override
protected UserDetails retrieveUser(final String username, final UsernamePasswordAuthenticationToken authentication) {
logger.info("AbstractUserDetailsAuthenticationProvider retrieveUser ");
final Object token = authentication.getCredentials();
String userName=null;
// 如果还处于本地有效期内,则不进行刷新
if(localCache.hasKey(String.valueOf(token))){
logger.info(" localCache ");
userName=(String)localCache.get(String.valueOf(token));
}else{
ResponseEntity<TokenInfoDto> resultRespo=null;
try {
resultRespo= tokenRestTemplate.refresh(String.valueOf(token),url,appId,clientId,clientSecret);
}catch (Exception ex){
logger.error("验证token出错",ex);
throw new UsernameNotFoundException("Cannot find user with authentication token=" + token);
}
if(resultRespo==null||(resultRespo.getStatusCodeValue()!= HttpServletResponse.SC_OK)){
throw new UsernameNotFoundException("Cannot find user with authentication token=" + token);
}
userName=resultRespo.getBody().getUser().getUsername();
localCache.set(String.valueOf(token),userName,1*60);// 本地缓存60分钟
}
UserDetails userDetails=new User(userName);
logger.info("AbstractUserDetailsAuthenticationProvider success");
return userDetails;
}
}
-
自定义的内存—缓存策略
首先使用map作为缓存容器,你就要考虑会不会内存溢出,所以给每一个值都设置一个过期时间,并且对数量进行控制是很有必要的, -
针对oom: 利用 软引用(接近oom 时,进行强制淘汰)+延时队列delayqueue去主动删除
-
针对数量主动删除值:LinkedList 去主动淘汰存储最久的值
自定义一个缓存对象 必须带有过期时间
public class DelayedCacheObject implements Delayed {
private final String key; // 缓存的key
private final SoftReference<Object> reference;// 软引用包装存储对象
private final long expiryTime; // 缓存时间
public DelayedCacheObject(String key, SoftReference<Object> reference, long expiryTime) {
this.key = key;
this.reference = reference;
this.expiryTime = expiryTime;
}
public String getKey() {
return key;
}
public SoftReference<Object> getReference() {
return reference;
}
@Override
public long getDelay(TimeUnit unit) {
return unit.convert(expiryTime - System.currentTimeMillis(), TimeUnit.MILLISECONDS);
}
@Override
public int compareTo(Delayed o) {
return Long.compare(expiryTime, ((DelayedCacheObject) o).expiryTime);
}
}
自定义缓存容器操作类
1.虽然使用了 软引用和 延时队列来保证不会oom ,但是如果同时登陆的人超过几千,对机器的考验还是会很大,所以这里还加上一个 长度限制,
2.构造方法中 主动构造一个守护线程进行垃圾的回收-------一直对延时队列进行判断,是否有失效的
public class LocalCache implements ICache {
private final ConcurrentHashMap<String, SoftReference<Object>> CACHE_MAP = new ConcurrentHashMap<>();// 存储对象的缓存容器
private final DelayQueue<DelayedCacheObject> CLEANING_QUEUE = new DelayQueue<>();// 强制GC的延时队列
/**
* 这个记录了缓存使用的最后一次的记录,最近使用的在最前面
*/
private static final List<String> CACHE_USE_LOG_LIST = new LinkedList<>();
/**
* 缓存最大个数
*/
private static final Integer CACHE_MAX_NUMBER = 1000;
public LocalCache() {
//将回收过期的key的线程设置为守护线程
Thread cleanerThread = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
try {
DelayedCacheObject delayedCacheObject = CLEANING_QUEUE.take();
if(CACHE_MAP.containsKey(delayedCacheObject.getKey())){
CACHE_MAP.remove(delayedCacheObject.getKey(), delayedCacheObject.getReference());
}
if(CACHE_USE_LOG_LIST.contains(delayedCacheObject.getKey())){
CACHE_USE_LOG_LIST.remove(delayedCacheObject.getKey());
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
});
cleanerThread.setDaemon(true);
cleanerThread.start();
}
@Override
public void set(String key, Object value, int periodInMinute) {
if (key == null) {
return;
}
if (value == null) {
CACHE_MAP.remove(key);
CACHE_USE_LOG_LIST.remove(key);
} else {
checkSize();// 先检查已经缓存的数量
saveCacheUseLog(key);
long expiryTime = System.currentTimeMillis() + periodInMinute*60*1000L;
SoftReference<Object> reference = new SoftReference<>(value);
CACHE_MAP.put(key, reference);
CLEANING_QUEUE.put(new DelayedCacheObject(key, reference, expiryTime));
}
}
@Override
public boolean hasKey(String key) {
return CACHE_MAP.containsKey(key);
}
@Override
public void remove(String key) {
CACHE_MAP.remove(key);
CACHE_USE_LOG_LIST.remove(key);
}
@Override
public Object get(String key) {
return Optional.ofNullable(CACHE_MAP.get(key)).map(SoftReference::get).orElse(null);
}
/**
* 检查大小
* 当当前大小如果已经达到最大大小
* 首先删除过期缓存,如果过期缓存删除过后还是达到最大缓存数目
* 删除最久未使用缓存
*/
private void checkSize() {
if (CACHE_USE_LOG_LIST.size()>= CACHE_MAX_NUMBER) {
String cacheKey = null;
synchronized (CACHE_USE_LOG_LIST) {
cacheKey = CACHE_USE_LOG_LIST.remove(CACHE_USE_LOG_LIST.size() - 1);
}
if (cacheKey != null) {
remove(cacheKey);
}
}
}
/**
* 保存缓存的使用记录
*/
private void saveCacheUseLog(String cacheKey) {
synchronized (CACHE_USE_LOG_LIST) {
CACHE_USE_LOG_LIST.remove(cacheKey);
CACHE_USE_LOG_LIST.add(0,cacheKey);
}
}
}